Back to Blog
tutorialIntermediate10 min read

Caddy Installation Guide for Proxmox 9 LXC

Step-by-step guide to installing Caddy as a reverse proxy in a Proxmox VE 9 LXC container with Debian 13, including DNS challenge setup with Cloudflare for valid SSL certificates.

Caddy Installation Guide for Proxmox 9 LXC (Debian 13)

How I Use Caddy

  • Reverse proxy. Instead of 192.168.10.100 for Pi-hole, I can use pihole.hake.rodeo.
  • DNS Challenge to get valid SSL certificates

Prerequisites

  • Proxmox VE 9.0.x installed and running
  • SSH or console access to Proxmox host
  • Available static IP address on your network (e.g., 192.168.10.103)
  • Your network gateway IP address
  • Cloudflare API token (optional DNS challenge section)
  • Pi-hole installed (optional DNS challenge section)

Network Configuration Required

Before starting, determine:

  • Desired Caddy IP: An unused static IP (e.g., 192.168.10.103)
  • Gateway (typically router) IP: Your router's IP (e.g., 192.168.10.1)
  • Subnet Mask: Usually /24 for home networks

Step 1: Create the LXC

Update templates

pveam update

Identify the correct version of Debian

pveam available | grep debian | grep -v turnkey

This guide will use the latest Debian 13 (update for your case)

pveam download local debian-13-standard_13.1-2_amd64.tar.zst

Create the container

pct create 103 local:vztmpl/debian-13-standard_13.1-2_amd64.tar.zst \
  --hostname caddy \
  --memory 512 \
  --cores 1 \
  --rootfs local-lvm:4 \
  --net0 name=eth0,bridge=vmbr0,ip=192.168.10.103/24,gw=192.168.10.1 \
  --unprivileged 1 \
  --onboot 1 \
  --start 1

Parameters explained:

  • 103: Container ID (must be unique; adjust if already in use)
  • --memory 512: 512MB RAM (sufficient for Caddy)
  • --cores 1: Single CPU core (sufficient for home use)
  • --rootfs local-lvm:4: 4GB disk space
  • --net0: Static IP configuration
  • --unprivileged 1: Runs as unprivileged container for better security (limits root access to host system)
  • --onboot 1: Auto-start on system boot
  • --start 1: Start immediately after creation

Important adjustments:

  • Container ID (103): Change if already in use (check with pct list)
  • IP Address: Replace 192.168.10.103/24 with your desired static IP
  • Gateway: Replace 192.168.10.1 with your network's gateway
  • Storage: Replace local-lvm if using different storage
  • Bridge: Replace vmbr0 if using different bridge (check with ip a | grep vmbr)

Verify the container is running

pct status 103

Step 2: Container Setup

Enter the container

pct enter 103

Set Locale to avoid warnings (paste all as one)

sed -i 's/^# en_US.UTF-8 UTF-8$/en_US.UTF-8 UTF-8/' /etc/locale.gen
locale-gen
update-locale LANG=en_US.UTF-8
export LANG=en_US.UTF-8

Update the container:

apt update && apt upgrade -y

Install required packages

apt install -y curl ca-certificates gnupg nano debian-keyring

Step 3: Install Caddy

Download Caddy GPG key

curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg

Create the sources file

nano /etc/apt/sources.list.d/caddy-stable.sources

Then paste in

Types: deb deb-src
URIs: https://dl.cloudsmith.io/public/caddy/stable/deb/debian
Suites: any-version
Components: main
Signed-By: /usr/share/keyrings/caddy-stable-archive-keyring.gpg

Set proper permissions

chmod 644 /etc/apt/sources.list.d/caddy-stable.sources
chmod 644 /usr/share/keyrings/caddy-stable-archive-keyring.gpg

Update apt

apt update

Install Caddy

apt install caddy

Verify installation

caddy version

Caddy should now be running. In your browser navigate to http://192.168.10.103:80. You should see the default Caddy page.

Step 4: Optional DNS Challenge

I have a domain registered on Cloudflare, which I use with Caddy to get a wildcard certificate.

Go to Cloudflare and generate an API Token. You can do this under your profile > API Tokens > Edit zone DNS > Use template

Then under zone resources select your domain.

Download a custom-built Caddy binary from Caddy's official build server

curl -o caddy 'https://caddyserver.com/api/download?os=linux&arch=amd64&p=github.com%2Fcaddy-dns%2Fcloudflare'

Stop Caddy service

systemctl stop caddy

Make new Caddy binary executable

chmod +x caddy

Replace existing binary

mv caddy /usr/bin/caddy

Start the new binary

systemctl start caddy

Create a .env file to store your Cloudflare API token

nano /etc/caddy/.env

Enter your API Token

CF_API_TOKEN=your_cloudflare_api_token_here

Set proper permissions

chmod 600 /etc/caddy/.env
chown caddy:caddy /etc/caddy/.env

Then update the systemd service to use the .env file - add the following

systemctl edit caddy

Look for the line:

### Anything between here and the comment below will become the contents of the drop-in file

Below it, add:

[Service]
EnvironmentFile=/etc/caddy/.env

If you do not add it in this section, it will not be applied.

Press CTRL+X then Y to save and exit

Now open the Caddyfile to configure the DNS challenge and reverse proxies.

nano /etc/caddy/Caddyfile

This is a global options block that tells Caddy: "For ALL sites in this Caddyfile, use Cloudflare DNS challenge to get SSL certificates." Use global when all your domains share the same TLS requirements. Skip global when you need different configurations per site.

{ 
	acme_dns cloudflare {env.CF_API_TOKEN}
}

Then below it add your route (replace hake.rodeo with your domain)

pihole.hake.rodeo { 
	reverse_proxy 192.168.10.100:80 
}

dashboard.hake.rodeo { 
	reverse_proxy 192.168.10.102:80 
}

Then CTRL+X then Y to save and exit. Then run

systemctl daemon-reload

And restart Caddy

systemctl restart caddy

Finally, ensure that Pi-hole's local DNS records have been configured to point to your reverse proxy (Caddy) for each service's domain. This assumes Caddy is on 192.168.10.103:

pihole.hake.rodeo → 192.168.10.103
dashboard.hake.rodeo → 192.168.10.103

How It All Works Together

  • DNS Resolution: Browser asks Pi-hole "What's the IP for pihole.hake.rodeo?"
  • Pi-hole Response: Pi-hole returns Caddy's IP (192.168.10.103)
  • HTTPS Connection: Browser connects to Caddy on port 443 (HTTPS)
  • Certificate: Caddy presents valid SSL certificate (obtained via DNS challenge)
  • Reverse Proxy: Caddy forwards request to backend (192.168.10.100:80)
  • Response: Backend responds to Caddy, Caddy returns to browser

Security Considerations

  • Caddy automatically obtains and renews SSL certificates
  • The .env file contains sensitive credentials - never commit to git
  • Consider firewall rules: ufw allow 80/tcp and ufw allow 443/tcp
  • Regularly update: apt update && apt upgrade caddy

Next Steps

  • Add more services to your Caddyfile
  • Monitor logs: journalctl -u caddy -f
  • Consider adding basic auth for sensitive services
  • Explore Caddy modules: https://caddyserver.com/docs/modules/