I run a small WordPress site from a Linux box under my desk. The box cost me about the same as a year of managed hosting from a major provider, and it pays for itself in twelve months. The ISP gives me a public IP. A free dynamic DNS service (DuckDNS) ties a hostname to that IP. Apache serves the pages. MariaDB holds the data. The whole stack runs unattended and has not gone down in three months. The total ongoing cost is the electricity for one small machine, which works out to a few dollars a month.
If you have a Linux machine, a public IP, and a couple of hours, you can do this too. The setup is not hard, but it has more moving parts than clicking “Deploy” on a managed platform. The trade is money for control and learning.
What you need before you start
There are three hard requirements and one soft one.
- A public IP address. Most home internet plans assign one by default. You can check yours at whatismyip.com. If you do not have a public IP, you are behind carrier-grade NAT (a setup where your ISP shares one IP across many customers), and you cannot host from home without a tunnel. Most US and EU residential ISPs do not CGN by default, but some fiber and mobile providers do. Check first.
- A static IP or a dynamic DNS client. Home IPs change after a reboot. Your ISP will sell you a static IP for $5 to $20 per month. If you would rather not pay, DuckDNS gives you a hostname like
mysite.duckdns.orgthat auto-updates to point at your current IP every five minutes. The hostname is free. The client script that updates it is one curl call. - A Linux machine that runs while the site is up. A refurbished desktop, a used mini-PC, a Raspberry Pi 4 or 5, an old laptop with the screen closed. The site only needs about 1 GB of RAM and a dual-core CPU. Apache plus WordPress will run on any of these. If the box reboots, the site goes down until it comes back, so pick hardware that does not need weekly reboots.
- A registered domain (optional). You can host on the DuckDNS hostname directly. If you want
yourname.com, you buy a domain (about $10 per year at most registrars) and point it at the DuckDNS hostname via CNAME.
Before you start, walk through this quick check:
- [ ] Confirm your public IP at whatismyip.com
- [ ] Confirm your ISP does not prohibit home servers in the terms
- [ ] Pick a DuckDNS subdomain and copy the token
- [ ] Decide which machine will run Apache and confirm it stays on
Setting up DuckDNS
DuckDNS gives you a fixed hostname that survives your IP changing. Set it up before anything else, because the hostname shows up in every later config file.
- Go to duckdns.org, sign in with your Google or GitHub account, and register a subdomain. Pick something you can live with for a while. The hostname is yours until you delete it.
- Copy the token from the dashboard. It is a long alphanumeric string. Treat it like a password.
- On the Linux box, create the update script:
mkdir -p ~/duckdns
echo 'echo url="https://www.duckdns.org/update?domains=mysite&token=YOUR_TOKEN&ip=" | curl -k -o ~/duckdns/duck.log -K -' > ~/duckdns/duck.sh
chmod 700 ~/duckdns/duck.sh
Replace mysite with your subdomain and YOUR_TOKEN with the token from the dashboard. Test it immediately:
~/duckdns/duck.sh
cat ~/duckdns/duck.log
A response of OK means DuckDNS accepted the update. Now schedule it with cron so it runs every five minutes:
crontab -e
# Add this line:
*/5 * * * * ~/duckdns/duck.sh >/dev/null 2>&1
After five minutes, your hostname resolves to your current IP. If your IP changes (reboot, ISP maintenance), DuckDNS catches up within five minutes. The site does not go down.
Installing Apache
Apache is the web server (a long-running program that listens on port 80 or 443 and serves files in response to HTTP requests). It is free, open source, and runs on every Linux distribution. Install it:
sudo apt install apache2 # Debian, Ubuntu, Mint
sudo dnf install httpd # RHEL, Fedora, Rocky, Alma
Start the service and enable it on boot:
sudo systemctl enable --now apache2
sudo systemctl status apache2
If status shows active (running), you have a working web server. Visit http://localhost in a browser on the Linux box. You should see the default Apache test page. From any other machine on your network, visit http://<your-local-IP> to confirm the box is reachable. If neither works, your firewall is blocking port 80. Open it:
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw reload
Port forwarding on your router is the next step. Forward external ports 80 and 443 to the local IP of the Linux box. Every router is different; the screen is usually under “Advanced” or “NAT” settings. Once the forward is in place, visiting http://mysite.duckdns.org from a phone on cellular should serve the Apache test page.
Installing MariaDB
MariaDB is the database server (a program that stores and retrieves your site’s content, user accounts, comments, and settings). WordPress uses it for everything that is not a static file. Install and secure it:
sudo apt install mariadb-server # Debian, Ubuntu
sudo dnf install mariadb-server # Fedora, Rocky, RHEL
sudo mysql_secure_installation
The mysql_secure_installation script asks you to set a root password, remove anonymous users, disallow remote root login, and remove the test database. Say yes to all of it. The defaults are sensible for a home server.
Then create a database and a user for WordPress:
CREATE DATABASE wordpress CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'wpuser'@'localhost' IDENTIFIED BY 'a-strong-password';
GRANT ALL PRIVILEGES ON wordpress.* TO 'wpuser'@'localhost';
FLUSH PRIVILEGES;
EXIT;
Save the database name (wordpress), the username (wpuser), and the password somewhere you will not lose. The WordPress installer needs all three.
Installing WordPress
WordPress is the content management system (the software that lets you write posts, manage pages, handle comments, and theme the site without writing HTML by hand). It runs on top of Apache and MariaDB. Install it:
cd /tmp
wget https://wordpress.org/latest.tar.gz
tar -xzf latest.tar.gz
sudo mv wordpress /var/www/wordpress
sudo chown -R www-data:www-data /var/www/wordpress
sudo chmod -R 755 /var/www/wordpress
The www-data user is Apache’s identity on Debian-based systems. On RHEL it is apache. Adjust the chown command to match.
Point Apache at the new directory by creating a virtual host config:
sudo nano /etc/apache2/sites-available/wordpress.conf
Paste this, replacing mysite.duckdns.org with your real hostname:
<VirtualHost *:80>
ServerName mysite.duckdns.org
DocumentRoot /var/www/wordpress
<Directory /var/www/wordpress>
AllowOverride All
Require all granted
</Directory>
</VirtualHost>
Enable it, disable the default site, and reload Apache:
sudo a2ensite wordpress
sudo a2dissite 000-default
sudo systemctl reload apache2
Visit http://mysite.duckdns.org in a browser. The WordPress installer walks you through the database connection, the site title, the admin user, and the admin password. The whole thing takes about ten minutes.
Adding HTTPS with Let’s Encrypt
Plain HTTP serves the site, but every modern browser flags it as “Not Secure.” Adding HTTPS is two commands:
sudo apt install certbot python3-certbot-apache # Debian, Ubuntu
sudo dnf install certbot python3-certbot-apache # Fedora, Rocky, RHEL
sudo certbot --apache -d mysite.duckdns.org
Certbot talks to Let’s Encrypt (a free, automated certificate authority that issues HTTPS certificates at no cost), proves you control the hostname by reaching your Apache server, and installs the certificate. It also sets up auto-renewal as a systemd timer. Your site is now https://mysite.duckdns.org and shows the padlock icon.
If you later add a real domain, run certbot --apache -d yourname.com again for the new hostname.
Trade-offs
Self-hosting at home is not free in time. The setup takes an afternoon if you have not done it before, and longer if you have to debug router quirks, ISP CGN, or a misconfigured firewall. Maintenance is small but real: WordPress core updates come out monthly, themes and plugins weekly, and MariaDB security patches every few months. Budget an hour a month for the boring work.
Power and ISP costs are the ongoing expense. A small box under a desk draws about $5 to $15 per month in electricity, depending on the hardware and your local rate. Your ISP probably allows residential servers in the fine print, but some fiber plans prohibit them. Read the terms.
Uptime is what you give up versus a real host. A $4 per month VPS gets you 99.9% uptime, redundant power, and a datacenter network. Your home setup gets you whatever your ISP and your power grid give you. For a personal blog or a small business site, that is fine. For an e-commerce site with daily revenue, it is not.
If you want a low-stakes place to learn Linux, Apache, MariaDB, and WordPress, hosting at home is a clear win. If you need uptime and you do not want to think about it, a managed host or a $4 VPS is the cheaper answer once you add your time.
Bottom line
A personal site on a home Linux box is a weekend project that costs almost nothing to run. DuckDNS gives you a stable hostname for free. Apache, MariaDB, WordPress, and Let’s Encrypt are all free and well-documented. The box sits under your desk and serves the site without your attention. After three months of running mine, the only thing I have done is apply WordPress updates and let certbot auto-renew. The site has been up.
If you only do one thing from this article, set up DuckDNS on your home box and forward ports 80 and 443 to it. Once that works, every later step has a foundation. The rest can wait.