🛠️ Tutorials · ⏱ 8 min read

Automated HTTPS Reverse Proxy with Caddy and Docker in 2026 (Effortless Let's Encrypt Certs)

2026 tutorial on deploying a Caddy reverse proxy with Docker: automatic HTTPS and Let's Encrypt certificates, multi-service routing, security headers, and seamless renewal. Ready-to-use Caddyfile and docker-compose configs.

S By Selfhostr Team · independent tests
Automated HTTPS Reverse Proxy with Caddy and Docker in 2026 (Effortless Let's Encrypt Certs)
ⓘ This article may contain affiliate links (no extra cost to you, it supports our tests). See the disclosure.
📝
2-3 lines
Config Lines
🔒
Automatic
HTTPS Setup
🔄
Zero config
Cert Renewal
🚪
80, 443
Required Ports

👍 What we like

  • Automatic HTTPS with Let's Encrypt without manual ACME config
  • Significantly simpler configuration than Nginx or Certbot
  • Built-in security headers and HTTP to HTTPS redirection
  • Clean service exposure via Docker network internal DNS

👎 What to watch

  • Requires DNS records to propagate before certificate issuance
  • Needs ports 80 and 443 open on the host firewall
  • Requires pre-installed Docker and Docker Compose
  • Dependent on external domain name ownership for validation
📑 Contents

You host several services on your VPS (a Nextcloud, an n8n, a website, an admin panel) and end up with URLs like http://ip:8080, http://ip:5678, without HTTPS and without proper domain names. It’s time to install a reverse proxy. And in 2026, if you want HTTPS that “just works,” Caddy is unbeatable: it automatically obtains and renews Let’s Encrypt certificates, without writing a single line of ACME configuration.

Where Nginx requires dozens of lines of config and a cron job for certbot, Caddy does the same thing in two or three lines. In this tutorial, we deploy Caddy via Docker, use it to cleanly expose multiple services behind a single entry point, with automatic HTTPS, security headers, and HTTP → HTTPS redirection. Everything is in versioned, reproducible files.

Prerequisites

  • A secure VPS running Ubuntu 24.04 (or Debian 12). If not already done, follow our guide to install and secure an Ubuntu VPS first.

  • Docker and Docker Compose installed (the installation command is in Step 1).

  • A domain name whose DNS you control (with your registrar, or via Cloudflare). We will use exemple.fr as the example domain.

  • Ports 80 and 443 open on your UFW firewall: this is essential for Let’s Encrypt validation and HTTPS traffic.

  • One or more already containerized services to expose (we will use Nextcloud and a demo web service as examples).

Step 1: Install Docker and Docker Compose

If Docker is not yet present, install it from the official repository (Ubuntu repository versions are often outdated):


sudo apt update

sudo apt install -y ca-certificates curl

sudo install -m 0755 -d /etc/apt/keyrings

sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc

sudo chmod a+r /etc/apt/keyrings/docker.asc

echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

sudo apt update

sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

Add your user to the docker group to avoid typing sudo for every command (log out and back in afterwards to apply changes):


sudo usermod -aG docker $USER

Verify the installation:


docker --version && docker compose version

Step 2: Configure DNS Records

Caddy needs your subdomains to point to your VPS’s IP address to validate certificates. With your DNS provider, create A records (and AAAA if you have IPv6):

| Type | Name | Value |

| :--- | :--- | :--- |

| A | cloud.exemple.fr | 203.0.113.10 |

| A | app.exemple.fr | 203.0.113.10 |

Wait for propagation (usually a few minutes, sometimes up to an hour). Verify from your machine:


dig +short cloud.exemple.fr

The command should return your VPS’s IP. Until it does, Let’s Encrypt will fail to issue the certificate.

Step 3: Create the Shared Docker Network

For Caddy to reach your containers by their name (rather than a changing IP), all services must share a dedicated Docker network. Create it once:


docker network create web

This web network serves as the “bus” between the reverse proxy and all your services. Caddy will resolve nextcloud:80 or demo:80 automatically via Docker’s internal DNS.

Step 4: Write the Caddyfile

The core of the configuration lies in a single file: the Caddyfile. Create a working directory and the file:


mkdir -p ~/caddy && cd ~/caddy

nano Caddyfile

Here is a complete Caddyfile exposing two services over HTTPS with security headers:


{

    # Email for Let's Encrypt notifications (expiration, etc.)

    email admin@exemple.fr

}



# Reusable configuration block for security headers

(securite) {

    header {

        Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"

        X-Content-Type-Options "nosniff"

        X-Frame-Options "SAMEORIGIN"

        Referrer-Policy "strict-origin-when-cross-origin"

        # Hide the server signature

        -Server

    }

}



cloud.exemple.fr {

    import securite

    reverse_proxy nextcloud:80

}



app.exemple.fr {

    import securite

    reverse_proxy demo:80

}

A few explanations:

  • Each block starting with a domain name automatically triggers the issuance of a Let’s Encrypt certificate and HTTP → HTTPS redirection. You don’t need to configure anything else.

  • reverse_proxy nextcloud:80 redirects traffic to the container named nextcloud, on its internal port 80. Docker handles the name resolution.

  • The (securite) snippet, imported into each block, factors out recommended HTTP headers. Useful for maintaining consistency.

Common pitfall: Never put http:// or https:// before the domain name in a site block. Caddy handles the protocol automatically. Writing https://cloud.exemple.fr disables automatic certificate issuance.

Step 5: The Caddy docker-compose.yml

In the same ~/caddy directory, create the composition file:


nano docker-compose.yml

services:

  caddy:

    image: caddy:2-alpine

    container_name: caddy

    restart: unless-stopped

    ports:

      - "80:80"

      - "443:443"

      - "443:443/udp"   # HTTP/3 (QUIC)

    volumes:

      - ./Caddyfile:/etc/caddy/Caddyfile:ro

      - caddy_data:/data

      - caddy_config:/config

    networks:

      - web



volumes:

  caddy_data:

  caddy_config:



networks:

  web:

    external: true

Important points:

  • The caddy_data volume is critical: it stores your certificates and ACME keys. Without it, you would request a certificate every time you restart and quickly hit Let’s Encrypt rate limits (5 identical certificates per week).

  • The network is declared external: true because we created it manually in Step 3.

  • The 443/udp port enables HTTP/3, natively supported by Caddy.

Start Caddy:


docker compose up -d

Follow the logs to watch certificate issuance in real-time:


docker compose logs -f caddy

You should see lines like certificate obtained successfully for each of your domains.

Step 6: Connect Your Services to Caddy’s Network

For a service to be reachable by Caddy, it must be on the web network and bear the name used in the Caddyfile. Here is an example of a demo service to expose. In another directory:


services:

  demo:

    image: nginxdemos/hello

    container_name: demo

    restart: unless-stopped

    networks:

      - web



networks:

  web:

    external: true

Note the absence of a ports: section: this is the whole point of the reverse proxy. The service is never exposed directly to the Internet; it is only accessible through Caddy via the internal web network. This is safer and cleaner.

Start it:


docker compose up -d

Visit https://app.exemple.fr: you get the demo service over HTTPS, with a valid padlock. For Nextcloud, simply ensure its container is named nextcloud and joins the web network (see our tutorial hosting Nextcloud on a VPS).

Step 7: Reload Configuration Without Downtime

When you add a service or modify the Caddyfile, reload without restarting the container (zero downtime, existing certificates are preserved):


docker compose exec caddy caddy reload --config /etc/caddy/Caddyfile

First validate the syntax to avoid breaking production:


docker compose exec caddy caddy validate --config /etc/caddy/Caddyfile

Final Verification

Check that everything is in place:


# Caddy is running

docker compose ps



# HTTP correctly redirects to HTTPS (code 308)

curl -sI http://cloud.exemple.fr | head -1



# The certificate is valid and the TLS chain is correct

curl -sI https://cloud.exemple.fr | head -1

For a complete TLS configuration audit, test your domain on SSL Labs: you should aim for an A or A+ grade thanks to HSTS headers and modern protocols enabled by default in Caddy.

Best Practices and Advanced Options

  • DNS Challenge Validation. If you want wildcard certificates (*.exemple.fr) or your server is not reachable on port 80, use the DNS challenge. This requires a Caddy image compiled with your DNS provider’s module (Cloudflare, OVH…) and an API token.

  • Frontend Authentication. To protect a sensitive service, Caddy handles basic_auth (bcrypt-hashed password) directly in the Caddyfile, or you can couple it with an SSO provider like Authelia.

  • Compression and Cache. Add encode gzip zstd in a site block to automatically compress responses.

  • Structured Logs. The log directive allows writing JSON logs usable by your monitoring stack.

  • Do not mix reverse proxies. Never expose a service directly (via ports:) and behind Caddy in parallel: this creates unencrypted access paths. Everything must go through the proxy.

FAQ

Why choose Caddy over Nginx or Traefik?

For the simplicity of automatic HTTPS. Caddy obtains, installs, and renews Let’s Encrypt certificates without any manual configuration or cron jobs, whereas Nginx requires certbot and verbose config blocks. Traefik is excellent for auto-discovery via Docker labels, but its learning curve is steeper. For a detailed comparison of the three, see Caddy vs Nginx vs Traefik in 2026.

What happens if Let’s Encrypt fails to issue the certificate?

In 90% of cases, it’s a DNS issue (the domain hasn’t pointed to the VPS yet) or a firewall issue (port 80 closed). Check dig +short your-domain and sudo ufw status. Caddy logs (docker compose logs caddy) indicate the exact cause of the ACME rejection. While debugging, use Let’s Encrypt’s staging environment to avoid hitting rate limits.

Are my certificates renewed automatically?

Yes, completely. Caddy continuously checks expiration and renews each certificate approximately 30 days before it expires, without any intervention. This is precisely what makes this solution “set and forget.” The only condition: the caddy_data volume must be properly persistent.

Can I use Caddy behind Cloudflare?

Yes. Enable “Full (strict)” mode in Cloudflare so that Cloudflare → VPS traffic remains encrypted with Caddy’s Let’s Encrypt certificate. If you enable Cloudflare’s orange proxy, the HTTP-01 challenge may fail; then switch to Cloudflare’s DNS challenge for certificate issuance.

Does Caddy consume many resources?

No, it’s one of its strengths. The caddy:2-alpine image weighs only a few dozen MB, and the container runs quietly with less than 50 MB of RAM for moderate traffic. It is perfectly suited for a small entry-level VPS.

With this reverse proxy in place, you can now stack as many services as you want, each with its own HTTPS subdomain, without ever touching certificate management again. The next step to sleep soundly: encrypting and automating backups of this server. To follow new vulnerabilities and self-hosted tools, subscribe to our Telegram watch bot.

Tags: CaddyDockerLet's EncryptReverse ProxyHTTPSDevOps

Related

🛠️ Tutorials

Deploy Immich with Docker in 2026: Complete Guide (HTTPS, Reverse Proxy, Backups)

Step-by-step 2026 tutorial to self-host Immich, the Google Photos alternative, using Docker Compose. Features include photo/video gallery, facial recognition, automatic HTTPS via Caddy and Let's Encrypt, backups, and hardening. Ready-to-use configs included.

Read
🛠️ Tutorials

Deploy Jellyfin with Docker in 2026: Complete Guide (Hardware Transcoding, HTTPS, Reverse Proxy)

Step-by-step 2026 tutorial to self-host Jellyfin, the open-source media server, using Docker Compose. Covers film/series libraries, Intel/NVIDIA hardware transcoding, automatic HTTPS via Caddy and Let's Encrypt, remote access, and hardening. Ready-to-use configs included.

Read
🛠️ Tutorials

Self-Hosting Your Website in 2026: Complete Guide (VPS, Docker, HTTPS)

2026 technical guide to self-hosting on a VPS: choosing plans, Docker setup, Let's Encrypt HTTPS, security, and real costs. Compare self-hosting vs. cloud.

Read