Production Operations on a Single Server · 3/10

Host Multiple Websites on One Server with Caddy

What will this help you do?

Route each domain to its own local service with Caddy. Check DNS, upstream addresses, HTTPS, configuration validation, safe reloads, and every affected site.

Who is it for?

Readers working through this problem and checking each step.

Before you start

Check the scope of this guide, then prepare the environment and materials it actually calls for.

Updated

On this page

Share an entry point without sharing every failure

One server can host several low-traffic websites. Caddy uses each request's host name to send it to the right local application. The risk is that every domain depends on the same proxy configuration: a syntax error while adding a new site can affect an old one. Treat each change as a sequence of backup, formatting, validation, reload, and checks for every site.

Before changing the proxy, make sure each application runs reliably under systemd or in a container and listens on a different local port. A reverse proxy cannot fix an upstream process that keeps exiting.

Check DNS and the ports needed for certificates

Each domain's A and AAAA records should point to this server, and ports 80 and 443 need to be allowed by both the cloud security group and the system firewall. If you publish an AAAA record, IPv6 must really reach the server; a wrong record can break access for some visitors and interfere with certificate validation.

Check records with dig or the authoritative DNS panel, not only a cached result on your laptop. You can prepare a site block while new records propagate, but restarting Caddy repeatedly will not make DNS propagation faster.

Give each domain a clear site block

caddyfile
www.example.com {
    encode zstd gzip
    reverse_proxy 127.0.0.1:3000
}

admin.example.com {
    encode zstd gzip
    reverse_proxy 127.0.0.1:3001
}

An upstream normally names a host and port, not a path such as /api appended to the address. For path-based routing, use handle or handle_path and be explicit about whether the prefix is removed. The application must also understand its real external host and scheme, especially for login callbacks and forwarded headers from a trusted proxy.

Split configuration only when the result remains easy to verify

With more sites, import can load files such as /etc/caddy/sites/*.caddy. Confirm that only intended files are included and that backups contain both the main file and imported files. Name a file after its domain rather than new, final, or final2—those names do not reveal what is active.

Shared snippets can hold common headers or compression settings, but avoid abstracting too early. Upload limits, caching, WebSockets, and timeouts may differ between sites. Forcing every site into one shared setting makes a local requirement a global risk.

Validate before every reload

bash
sudo cp /etc/caddy/Caddyfile /etc/caddy/Caddyfile.backup
sudo caddy fmt --overwrite /etc/caddy/Caddyfile
sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy
sudo systemctl status caddy --no-pager

Stop if validation fails; do not reload a known-invalid file. For normal configuration changes, reload rather than stopping and starting the service. A reload replaces the configuration while reducing connection disruption. If the setup uses imported files, include those in the backup and validation scope.

Check every site, not only its homepage status

For each domain, check HTTP-to-HTTPS behavior, certificate host names, the homepage, health endpoint, static assets, and a login or form route where relevant. Confirm that requests for site A do not land on site B and that an unknown host does not expose the admin site.

bash
curl -I http://www.example.com
curl -I https://www.example.com
curl -fsS https://www.example.com/api/health
journalctl -u caddy --since '10 minutes ago' --no-pager

For a 502, request the local upstream directly before inspecting Caddy logs. For TLS errors, check DNS, ports 80/443, and certificate logs. The first-site Caddy deployment and HTTPS troubleshooting guides provide the earlier steps.

What counts as done?

Each domain reaches only its intended local port; application ports are not publicly open; configuration validates and is reloaded safely; and HTTPS, health, and essential functions pass for every site after the change. Next, apply the same boundaries to Docker Compose in production.

Sources

  1. Caddy reverse_proxy directiveCaddy Documentation
  2. Caddy Command LineCaddy Documentation

Next steps

Continue with the next useful task; you do not need to read everything at once.

  1. Deploy Open WebUI with Docker and Persistent Storage →
  2. Install Ollama on Linux and Expose It Safely →
  3. Deploy Dify with Docker Compose: A Production-minded Guide →
RESOURCES I USE · REFERRAL

Two services to compare when you are ready to launch

This is not an automated ranking, and neither service is necessary for everyone. These are services I use, with the use case and limitations kept visible.

Cloud server · Used for early projects

RainYun

A practical candidate for a website or small service. Choose by user region, configuration and measured workload rather than the lowest headline price.

Referral disclosure: these links contain my referral information. I may receive a platform benefit if you sign up or order, at no additional charge from AIOOS. Check the order page for current pricing, availability, regions and terms. Read the full affiliate disclosure