---
title: Caddy for Astro + Podman (HTTPS and Canonical Host)
description: How to put Caddy in front of a Podman-hosted Astro SSR app with automatic TLS and www redirect.
pubDate: '2026-03-03T15:53:00+01:00'
category: en/on-premises-private-cloud
tags:
  - podman
  - caddy
seriesParent: astro-ssr-with-podman
seriesOrder: 3
---

Once the Astro container was answering on `127.0.0.1:4321`, I still needed HTTPS and a single canonical hostname. I picked Caddy over nginx for exactly one reason — its built-in ACME client means I never touch `certbot` timers or renewal cron jobs.

This post is the thin layer that turns the private app port into a public HTTPS site.

## Install Caddy

Caddy isn't in the default Debian repos, so I added Cloudsmith's stable channel:

```bash
sudo apt update
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 >/dev/null
sudo apt update
sudo apt install -y caddy
```

## Reverse proxy and canonical host

**Problem:** Two DNS names pointed at the VPS — `adrian-altner.de` and `www.adrian-altner.de`. I wanted exactly one to live publicly, with the other 301-ing to it.

**Implementation:** Caddy's site blocks do both jobs at once — TLS issuance and the redirect — with no extra config.

```txt
# /etc/caddy/Caddyfile
www.adrian-altner.de {
  redir https://adrian-altner.de{uri} permanent
}

adrian-altner.de {
  encode zstd gzip
  reverse_proxy 127.0.0.1:4321
}
```

**Solution:** Caddy fetches Let's Encrypt certificates for both hostnames on first boot, serves `adrian-altner.de` via the reverse proxy, and answers `www.adrian-altner.de` with a permanent redirect.

## Validate and reload

```bash
sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl restart caddy
sudo systemctl enable caddy
sudo systemctl status caddy --no-pager
```

A formatting warning on validate is harmless — `caddy fmt` cleans it up in place:

```bash
sudo caddy fmt --overwrite /etc/caddy/Caddyfile
```

## Verifying the result

```bash
curl -I http://adrian-altner.de
curl -I https://adrian-altner.de
curl -I https://www.adrian-altner.de
```

What I was looking for:

- HTTP redirects to HTTPS (Caddy does this automatically once TLS is in place).
- `www` returns a 301 to the apex domain.
- The TLS certificate is trusted and issued by Let's Encrypt.

## Network hygiene

With Caddy as the only public entrypoint, there's no reason for port `4321` to be reachable from the outside world. Binding it to localhost in compose closes that door:

```yaml
ports:
  - "127.0.0.1:4321:4321"
```

Now the Node process only listens on the loopback interface — Caddy alone handles `80`/`443`.

## What to take away

- Caddy collapses "reverse proxy", "TLS", and "canonical host" into a handful of lines of config.
- Automatic HTTPS means one less cron job — no certbot, no renewal hooks.
- Bind the app container to `127.0.0.1` as soon as a proxy sits in front of it; there's no upside to exposing it publicly.
- One canonical hostname with the other redirecting avoids duplicate-content SEO surprises later.
