Hosting a website from home

· in fundamentals

Tested on Debian 13.7 (Docker) with nginx 1.26.3, Certbot 4.0.0 (package and renewal timer only), Caddy 2.11.9 from the Caddy repository, ufw 0.36.2 and unattended-upgrades 2.12 on 5 November 2026. Certificate issuance needs a public domain and was not tested.

This guide sets up a web server on a Debian machine at home and serves a static website from it over HTTPS. It gives two routes, nginx with Certbot or Caddy, then covers the firewall, publishing updates to the site and keeping the server patched.

Is hosting at home right for your site?

A static website (HTML, CSS and images, no database) is the easiest thing to host from home. It needs little memory or processor time, and a small machine can serve it to many visitors. Blogs made with a static site generator such as Hugo, Jekyll or Eleventy fall into this group.

Home hosting has limits you should accept before you start:

  • Upload speed. Your visitors download from your upload, which on many home connections is much slower than the download speed. A page with large images will feel slow if several people load it at once.
  • Uptime. When your power or internet goes down, so does the site. Nobody gets paged.
  • Your connection. You need a way for visitors to reach the server, which usually means a public IP address and two forwarded ports. Check your provider's terms too, since some home plans restrict running servers.

For a personal site, a project page or a small community, these limits are usually fine. For a shop or anything people depend on, rent hosting instead. The low-tech website guide shows how to build a site that is small enough to live with them.

What you need before you start

  • A machine running Debian 13 (any of the options in choosing home server hardware is enough for a static site), and a user account with sudo.
  • SSH access to it with keys, as set up in the SSH guide.
  • A domain name. Its A record (and AAAA record, if you use IPv6) must point to your home's public address. Use dynamic DNS if that address changes.
  • Ports 80 and 443 (TCP) forwarded from your router to the server. The port forwarding guide explains how, and what to do behind CGNAT.

Port 80 must stay open even though the site will run on HTTPS. Let's Encrypt's HTTP-01 check, which both routes below use, only connects on port 80.

The examples use example.org as the domain. Replace it with yours everywhere.

nginx or Caddy?

Both are open-source web servers that serve static files well. The main difference for a home server is how they get HTTPS certificates.

nginx + CertbotCaddy
InstallFrom Debian's own repositoryFrom Caddy's repository (Debian's package is version 2.6.2)
HTTPSCertbot gets the certificate and edits the nginx config; a timer renews itBuilt in: Caddy gets and renews certificates by itself
HTTP to HTTPS redirectCertbot can add itAutomatic
Config for one static siteAbout ten linesAbout four lines
Where help is easiest to findVery widely used, many examplesSmaller community, clear official docs

Pick Caddy if you want the fewest moving parts. Pick nginx if you already know it, or if you want everything from Debian's repository and its security updates. Follow one route, not both: they both want ports 80 and 443.

Put the site files in place

Both routes serve files from /var/www/example.org. Create the directory on the server and make your own account its owner, so you can upload without sudo:

sudo mkdir -p /var/www/example.org
sudo chown "$USER": /var/www/example.org
echo '<h1>It works</h1>' > /var/www/example.org/index.html

The web server only needs to read these files. Debian's default permissions (directories 755, files 644) allow that.

Route A: nginx with Certbot

Install nginx and add the site

sudo apt install nginx

Debian 13 installs nginx 1.26.3 and starts it with a default page. Site configurations live in /etc/nginx/sites-available/, and nginx loads the ones linked from /etc/nginx/sites-enabled/. Create /etc/nginx/sites-available/example.org:

server {
    listen 80;
    listen [::]:80;
    server_name example.org www.example.org;

    root /var/www/example.org;
    index index.html;

    location / {
        try_files $uri $uri/ =404;
    }
}

The two listen lines accept IPv4 and IPv6 on port 80. server_name lists the names this block answers to. try_files looks for a file matching the request, then a directory, and returns a 404 error if neither exists.

Enable the site, disable the default one, test the configuration and reload:

sudo ln -s /etc/nginx/sites-available/example.org /etc/nginx/sites-enabled/
sudo rm /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl reload nginx

Always run nginx -t before a reload. It prints syntax is ok and test is successful, or the file and line of the mistake, and a broken config never reaches the running server.

Check it on the server with curl -H "Host: example.org" http://localhost/. It should print your test page.

Add HTTPS with Certbot

Certbot is the Let's Encrypt client from the EFF. Its nginx plugin proves you control the domain over port 80, gets a certificate and adds the HTTPS settings to your server block:

sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d example.org -d www.example.org

The first run asks for an email address and for agreement to the terms. Certbot then edits /etc/nginx/sites-available/example.org to add listen 443 ssl and the certificate paths. Add --redirect to the command if you want it to also send all HTTP visitors to HTTPS.

Renewal is automatic. The Debian package installs a systemd timer, certbot.timer, that runs twice a day with a random delay and renews certificates that are close to expiry. Check the timer and do a test renewal against the Let's Encrypt staging server:

systemctl list-timers certbot.timer
sudo certbot renew --dry-run

Route B: Caddy

Install Caddy from its repository

Debian 13's own caddy package is version 2.6.2. Caddy's project publishes current releases in its own apt repository. These are the commands from Caddy's install page:

sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo chmod o+r /usr/share/keyrings/caddy-stable-archive-keyring.gpg
sudo chmod o+r /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install caddy

If gpg is missing, install it with sudo apt install gpg first. The package installs Caddy as a systemd service and starts it with a default page on port 80. On 5 November 2026 this installed Caddy 2.11.9.

Configure the site

Replace the contents of /etc/caddy/Caddyfile with:

example.org, www.example.org {
	root * /var/www/example.org
	file_server
	encode zstd gzip
}

The first line names the sites. root sets the folder, file_server serves the files in it, and encode compresses responses for browsers that accept it. Check the file, then reload:

caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy

Because the site address is a public domain name, Caddy turns on HTTPS by itself. It gets a certificate from Let's Encrypt (falling back to ZeroSSL), renews it before it expires and redirects HTTP to HTTPS. That only works if the domain's DNS records point at your address and ports 80 and 443 reach the server from outside. If the certificate fails, sudo journalctl -u caddy shows the reason, which is almost always DNS or a blocked port.

Turn on the firewall

A firewall on the server blocks every port you haven't chosen to open, so a program you install later doesn't end up on the internet by accident. ufw is the simplest way to set one up on Debian.

Allow SSH before you enable the firewall. Enabling it first cuts off your SSH session, and you will need a keyboard and screen on the server to get back in.
sudo apt install ufw
sudo ufw allow SSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status verbose

The default policy blocks incoming traffic and allows outgoing traffic. The rules apply to IPv4 and IPv6 alike. With nginx, sudo ufw allow "Nginx Full" does the same as the two port lines, since the profile opens 80 and 443 over TCP. sudo ufw app list shows the profiles your installed programs provide.

Publish changes to the site

Build or edit the site on your own computer, then copy it to the server over SSH with rsync. With a homeserver entry in ~/.ssh/config, from the folder that holds your built site:

rsync -av --delete public/ homeserver:/var/www/example.org/

The trailing slash on public/ copies the folder's contents rather than the folder itself. --delete removes files on the server that no longer exist locally, so the server matches your copy exactly. Run it once with --dry-run to see what it will change. rsync must be installed on both machines. The web server serves the new files straight away, with no reload.

Keep the server updated

A web server that faces the internet needs security updates soon after they are released. Debian's unattended-upgrades package installs them for you:

sudo apt install unattended-upgrades

On Debian 13 the package turns itself on at install. Check that /etc/apt/apt.conf.d/20auto-upgrades contains these two lines, and run sudo dpkg-reconfigure unattended-upgrades to create the file if it doesn't:

APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";

By default it installs updates from Debian's main and security archives every day. That covers nginx and Certbot, which come from Debian. Packages from Caddy's repository are not included, so update Caddy with sudo apt update && sudo apt upgrade now and then, or add its origin to /etc/apt/apt.conf.d/50unattended-upgrades.

Some updates, such as a new kernel, only take effect after a reboot. Reboot the server once in a while, or after a kernel update, and check that the site comes back.

Check that it works from outside

Test from a network other than your own, such as a phone on mobile data, because many home routers can't reach their own public address from inside. Open https://example.org and check that:

  • the page loads and the browser shows a valid certificate;
  • http://example.org redirects to HTTPS;
  • both example.org and www.example.org work.

If the site loads at home but not from outside, the problem is the port forward or CGNAT, not the web server. The port forwarding guide has a checklist for that. The same server can later host other services, such as an XMPP server, alongside the site.

Frequently asked questions

Can I host a website from home?

Yes, if your connection has a public IP address and you can forward ports 80 and 443 to your server. Expect limits from your upload speed and from power or internet outages, which suit a personal or small site better than one people depend on.

Should I use nginx or Caddy for a home web server?

Caddy gets and renews HTTPS certificates by itself and needs about four lines of config for a static site. nginx comes from Debian's own repository and uses Certbot for certificates, which suits you if you already know it.

Do I need port 80 open if my site uses HTTPS?

Yes, for the setups in this guide. Let's Encrypt's HTTP-01 check only connects on port 80, and both the Certbot and Caddy routes in this guide use it.

How do I renew a Let's Encrypt certificate with nginx?

On Debian you don't need to do it by hand. The certbot package installs a certbot.timer that runs twice a day and renews certificates close to expiry, and sudo certbot renew --dry-run tests that renewal works.

How do I keep a home web server secure?

Turn on a firewall that only allows SSH, 80 and 443, use key-only SSH login, and enable Debian's unattended-upgrades for security updates. Update software from other repositories, such as Caddy's, by hand.

Sources

  1. Beginner's guide, nginx documentation
  2. try_files directive, nginx documentation
  3. User guide, Certbot documentation
  4. Challenge types, Let's Encrypt
  5. Install, Caddy documentation
  6. Automatic HTTPS, Caddy documentation
  7. file_server directive, Caddy documentation
  8. ufw(8) manual, Debian
  9. UnattendedUpgrades, Debian Wiki