Your First Website: From Server to Search · 7/10

Deploy Your First Static Website to a Linux Server with Caddy

Upload a local HTML and CSS site to a safe web directory, install Caddy, configure the domain, validate the configuration and publish without exposing a development server.

0 reads

Confirm every prerequisite

Before copying files, confirm that the site works locally, the A record returns the real server IP, the deploy user can use sudo, and UFW allows SSH plus TCP ports 80 and 443. Replace example.com and 203.0.113.10 in every command. Keep the provider console available.

Caddy's official install page is the source of truth for current package-repository commands. On Debian or Ubuntu, configure the official stable repository exactly as documented, then install the package:

bash
sudo apt update
sudo apt install caddy
caddy version
systemctl status caddy --no-pager

The package normally installs a systemd service. Use that service in production rather than leaving caddy run attached to an SSH window.

Prepare a readable web root

Keep site files outside a user's home directory. Create a clear path and give the deploy user ownership while leaving the files world-readable for the unprivileged Caddy service.

bash
sudo install -d -m 0755 -o deploy -g deploy /var/www/my-first-site
ls -ld /var/www/my-first-site

Do not solve permission errors with chmod 777. A static site usually needs directories at 755 and files at 644; nobody other than the deployment account needs write access.

Upload from your own computer

Run this in the local my-first-site folder, not on the server. The trailing slashes matter: they copy the folder contents into the remote web root.

bash
rsync -av --progress ./ deploy@203.0.113.10:/var/www/my-first-site/

If rsync is unavailable, use scp -r ./* deploy@203.0.113.10:/var/www/my-first-site/. Then log into the server and confirm index.html and styles.css exist. Avoid an automatic --delete option until you understand exactly which remote files it would remove.

bash
find /var/www/my-first-site -maxdepth 2 -type f -print
namei -l /var/www/my-first-site/index.html

Configure the canonical domain

Back up the existing Caddyfile before editing it. This example serves the root domain and permanently redirects www to the root. If you prefer www as canonical, reverse the two blocks.

caddyfile
www.example.com {
  redir https://example.com{uri} permanent
}

example.com {
  root * /var/www/my-first-site
  encode zstd gzip
  file_server
}

Write it to /etc/caddy/Caddyfile with sudoedit. Do not include https:// before the domain in the site label unless you have a specific reason.

Validate, reload and inspect

Validation catches syntax errors without replacing the running configuration. Reload applies a valid change without a full service stop.

bash
sudo caddy fmt --overwrite /etc/caddy/Caddyfile
sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy
systemctl status caddy --no-pager
journalctl -u caddy --no-pager -n 80

Now test both names from your computer with curl -I https://example.com and curl -I https://www.example.com. The root should return 200, while www should return a permanent redirect to the root.

Publish updates safely

For the next change, preview locally, make a backup or commit, upload files, and check the public page. Static files can be copied without reloading Caddy. Reload only when the Caddyfile changes.

What I learned

My first deployment used a server inside my home folder and permissions that only my SSH user could read. I kept changing permissions until it worked, without knowing why. A fixed /var/www path, a dedicated deploy owner and a service that only needs read access make the setup much easier to reason about.

Finish line

The public page should load by domain, www should redirect consistently, Caddy should be active after you log out, and no Python preview process should be exposed. If HTTPS fails, do not randomly reinstall packages; use the layer-by-layer HTTPS troubleshooting guide.