# Caddy automatic HTTPS: install, configure and verify

> Install Caddy 2 from the official repository, serve HTTPS with automatic Let's Encrypt certificates, add DNS-01 for wildcards and check renewal.

Source: https://ssl.sh/guides/caddy-automatic-https/
Last updated: 2026-11-10

Caddy obtains and renews a certificate for every site address in the Caddyfile on its own and redirects HTTP to HTTPS, with no TLS configuration. Name the domain in the Caddyfile, keep ports 80 and 443 open, and reload Caddy.

The steps apply to Caddy 2.x on Debian, Ubuntu and RHEL-compatible Linux, with `example.com` and `www.example.com` as the site names. Check the Caddy docs for the current release.

## Prerequisites

Caddy does the ACME work itself, so it must own ports 80 and 443.

- A Linux server with systemd and sudo access.
- DNS `A`/`AAAA` records for `example.com` and `www.example.com` pointing at the server, for example `203.0.113.10`.
- Inbound TCP 80 and 443 open to all sources, and no other web server (Nginx, Apache) listening on them.

```bash
dig +short example.com A
sudo ss -ltnp | grep -E ':(80|443) '
```

## Step 1: Install Caddy from the official repository

The Caddy project publishes an apt repository for Debian and Ubuntu:

```bash
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
```

On Fedora and RHEL-compatible systems the docs use the `@caddy/caddy` COPR repository. The package installs a `caddy` systemd service that reads `/etc/caddy/Caddyfile`.

## Step 2: Write the Caddyfile

Naming a domain as the site address is all Caddy needs to enable HTTPS:

```caddy
# /etc/caddy/Caddyfile
{
    email admin@example.com
}

example.com, www.example.com {
    root * /var/www/example.com
    file_server
}
```

For an application behind Caddy, replace the two body lines with `reverse_proxy 127.0.0.1:3000`. The global `email` is optional; it is attached to the ACME account.

## Step 3: Validate and reload

Caddy requests the certificates as soon as it loads the new Caddyfile:

```bash
caddy fmt --overwrite /etc/caddy/Caddyfile
caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy
sudo journalctl -u caddy --no-pager -n 50 | grep -iE 'certificate|obtain|error'
```

The log shows `certificate obtained successfully` for each name within a few seconds.

## Step 4 (optional): Wildcards with the DNS-01 challenge

Wildcards need a DNS provider module, which the standard package does not include. Build Caddy with the module for your DNS host, for example Cloudflare:

```bash
xcaddy build --with github.com/caddy-dns/cloudflare
```

Then reference an API token from the environment of the Caddy service:

```caddy
*.example.com, example.com {
    tls {
        dns cloudflare {env.CF_API_TOKEN}
    }
    reverse_proxy 127.0.0.1:3000
}
```

To request a specific Let's Encrypt profile, add `issuer acme { profile tlsserver }` inside the `tls` block; the docs mark this option experimental. See [Let's Encrypt certificate profiles](https://ssl.sh/guides/lets-encrypt-certificate-profiles/).

## Verify

Both names should present a valid certificate, and plain HTTP should redirect to HTTPS. Check the result from outside with the [SSL checker](https://ssl.sh/tools/ssl-checker/), or from any terminal:

```bash
curl ssl.sh/example.com
```

Confirm the redirect and the served certificate:

```bash
curl -sI http://example.com | head -n 3
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null \
  | openssl x509 -noout -issuer -dates
```

## Automatic renewal

Renewal is built in and needs no cron job or timer. Caddy checks its certificates periodically, follows the CA's ARI renewal window when offered, and otherwise renews well before expiry, then hot-swaps the certificate without dropping connections. It will keep working when Let's Encrypt moves to 64-day certificates on February 10, 2027 and 45-day certificates on February 16, 2028.

What can break it: blocking port 80/443 after setup, moving DNS away, or deleting the data directory. Watch the journal for renewal errors and add the domain to [ssl.sh monitoring](https://ssl.sh/monitor/) (in development):

```bash
sudo journalctl -u caddy --since "7 days ago" | grep -i renew
```

## Troubleshooting

- **listen tcp :443: bind: address already in use**: another server holds the port; stop it or move it behind Caddy.
- **Timeout during connect** or **connection refused** in the logs: port 80/443 is blocked or DNS is wrong; see [certbot timeout during connect](https://ssl.sh/errors/certbot-timeout-during-connect/), which covers the same causes.
- **too many certificates already issued**: repeated restarts with a broken config hit Let's Encrypt limits; test with `acme_ca https://acme-staging-v02.api.letsencrypt.org/directory` and read [Let's Encrypt rate limits](https://ssl.sh/errors/lets-encrypt-rate-limit/).
- **CAA record prevents issuance**: your CAA records do not allow the CA Caddy used; see [CAA record prevents issuance](https://ssl.sh/errors/caa-record-prevents-issuance/).
- **Browser shows an internal certificate**: you used `tls internal` or a site address such as `localhost`; see [self-signed certificate errors](https://ssl.sh/errors/mozilla-pkix-error-self-signed-cert/).
