LocalHake

Tutorial

Caddy Installation Guide for Proxmox 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.

Hake HardwarePublished Updated ~9 minintermediate

This post contains paid links (affiliate) — how that works.


▶ Watch this build on YouTube
Contents

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

bash
pveam update

Identify the correct version of Debian

bash
pveam available | grep debian | grep -v turnkey

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

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

Create the container

bash
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

bash
pct status 103

Step 2: Container Setup

Enter the container

bash
pct enter 103

Set Locale to avoid warnings (paste all as one)

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

bash
apt update && apt upgrade -y

Install required packages

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

Step 3: Install Caddy

Download Caddy GPG key

bash
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

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

Then paste in

text
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

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

Update apt

bash
apt update

Install Caddy

bash
apt install caddy

Verify installation

bash
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

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

Stop Caddy service

bash
systemctl stop caddy

Make new Caddy binary executable

bash
chmod +x caddy

Replace existing binary

bash
mv caddy /usr/bin/caddy

Start the new binary

bash
systemctl start caddy

Create a .env file to store your Cloudflare API token

bash
nano /etc/caddy/.env

Enter your API Token

text
CF_API_TOKEN=your_cloudflare_api_token_here

Set proper permissions

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

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

bash
systemctl edit caddy

Look for the line:

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

Below it, add:

text
[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.

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

text
{ 
	acme_dns cloudflare {env.CF_API_TOKEN}
}

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

text
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

bash
systemctl daemon-reload

And restart Caddy

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

text
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/

Gear used