Skip to content
Melinhost

A reverse proxy with automatic HTTPS using Caddy: many apps, one file

Publish several apps on one server under their own domains; Caddy obtains and renews the certificates automatically.

Tutorials · 3 min read

caddy-reverse-proxy-https

If you run several web applications on one server, a reverse proxy publishes them all on ports 80 and 443 and routes by domain name. Caddy does this with one short file and obtains and renews HTTPS certificates by itself.

Reverse proxy architecture
The internet reaches Caddy on 443; Caddy forwards to the right application by domain name.

Prerequisites

  • Docker and Compose (see Installing Docker).
  • A domain or subdomain for each application, with an A record pointing at the server IP (see DNS records).
  • Ports 80 and 443 open in the firewall: sudo ufw allow 80,443/tcp

1. The Compose file

When Caddy and the applications are in the same Compose project they reach each other by service name; you don’t need to publish the applications’ ports outside.

services:
  caddy:
    image: caddy:2
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - caddy_data:/data
      - caddy_config:/config

  app:
    image: traefik/whoami
    restart: unless-stopped

volumes:
  caddy_data:
  caddy_config:

2. The Caddyfile

app.yourdomain.com {
    reverse_proxy app:80
}

# the same pattern for a second app:
# git.yourdomain.com {
#     reverse_proxy gitea:3000
# }

Each block is a domain name and the reverse_proxy target is a container name and port on the same network.

3. Start it

docker compose up -d
docker compose logs -f caddy

Caddy obtains the certificate automatically on the first request (Let’s Encrypt); the log shows “certificate obtained successfully”. Open https://app.yourdomain.com in your browser.

Adding a new application

  1. Add the application’s service to the Compose file (no need to publish a port on the host).
  2. Add a new domain block to the Caddyfile.
  3. Reload the configuration: docker compose exec caddy caddy reload --config /etc/caddy/Caddyfile

If the application is outside your Compose project, create a shared Docker network and attach both Caddy and the application to it (docker network create proxy and networks: [proxy] on each service).

Useful additions

NeedCaddyfile
Redirect www to the apex domainwww.yourdomain.com { redir https://yourdomain.com{uri} }
Compressionencode zstd gzip
Basic authenticationbasicauth (with a bcrypt hash)
Specific IPs onlymatch with @allowed remote_ip 203.0.113.0/24

Troubleshooting

  • Certificate could not be obtained: make sure the A record is right and ports 80/443 are open (see DNS change not showing).
  • 502 Bad Gateway: the target container isn’t running or the name/port is wrong: check docker compose ps and the reverse_proxy target.
  • Limits: Let’s Encrypt has certificate issuance rate limits; don’t repeatedly remove and re-add a domain while experimenting.

Start with Melinhost

For several applications, a cloud server with 4 vCPU and 8 GB RAM is a comfortable start.

Related posts

err-ssl-version-or-cipher-mismatch

· Tutorials

ERR_SSL_VERSION_OR_CIPHER_MISMATCH: causes and fixes

The browser can’t establish a secure connection: how to find and fix old TLS versions, incompatible ciphers, a missing certificate and wrong virtual-host settings.

Read more →

Cookie preferences

We use cookies required for the site to work. Analytics and marketing cookies are enabled only with your consent. Cookie Policy