# Caddy Reverse Proxy - [Caddy Reverse Proxy](#caddy-reverse-proxy) - [Custom Caddy Image](#custom-caddy-image) - [Install Caddy](#install-caddy) - [Ansible](#ansible) - [Manual](#manual) - [Adding a new Caddy Record](#adding-a-new-caddy-record) - [Logs](#logs) - [Caddy WAF](#caddy-waf) ## Custom Caddy Image This repo builds a custom caddy image with route53 DNS certbot support. ```bash podman image pull gitea.reeseapps.com/services/caddy:latest ``` To upgrade the image, check [the caddy-dns route53 project](https://github.com/caddy-dns/route53/tags) releases and update the `Containerfile` with the new version. ## Install Caddy ### Ansible You'll need a secrets/Caddyfile with your caddy config. `secrets/Caddyfile` example: ```conf https://something.reeseapps.com:443 { reverse_proxy internal.reeselink.com:8000 } https://something-else.reeseapps.com:443 { reverse_proxy internal-other.reeselink.com:8080 } ``` Make sure to add [your route53 configuration](https://github.com/caddy-dns/route53?tab=readme-ov-file#configuration) ```conf tls { dns route53 { access_key_id "..." secret_access_key "..." region "us-east-1" wait_for_route53_sync true skip_route53_sync_on_delete true route53_max_wait 2m max_retries 5 } } ``` The playbook limits the installer to `hosts: caddy` so make sure you have a caddy host in your inventory. Now you can install the Caddy service with something like: ```bash # Base Proxy ansible-playbook \ -i ansible/inventory.yaml \ active/container_caddy/install_caddy_proxy.yaml # Deskwork (AI) Proxy ansible-playbook \ -i ansible/inventory.yaml \ active/container_caddy/install_caddy_deskwork.yaml # Toybox (AI) Proxy ansible-playbook \ -i ansible/inventory.yaml \ active/container_caddy/install_caddy_toybox.yaml ``` See ansible playbook [install_caddy.yaml](/active/container_caddy/install_caddy.yaml) ### Manual As root ```bash mkdir /etc/caddy vim /etc/caddy/Caddyfile ``` Caddy will automatically provision certificates if the server DNS points to the correct IP and is accessible on the ports specifified. All you need to do is put `https` in the caddy conf. Example: ```conf # Gitea https://gitea.reeseapps.com:443 { reverse_proxy podman.reeselink.com:3000 } # Jellyfin https://jellyfin.reeseapps.com:443 { reverse_proxy podman.reeselink.com:8096 } ``` ```bash vim /etc/containers/systemd/caddy.container ``` ```conf [Unit] Description=Caddy [Container] AddCapability=NET_ADMIN ContainerName=caddy Image=docker.io/caddy:2 Network=host SecurityLabelDisable=true Volume=/etc/caddy:/etc/caddy Volume=caddy_data:/data Volume=caddy_config:/config [Service] Restart=always [Install] WantedBy=default.target ``` ```bash systemctl daemon-reload systemctl restart caddy ``` ## Adding a new Caddy Record Before you can create a Caddyfile you need records that point to your server. You can either create them manually in your DNS provider of choice or use the provided ddns service: 1. Update the [ddns caddy records](/active/container_ddns/secrets/caddy_records.yaml) 2. (Optional) Update the Caddyfile at `active/container_caddy/secrets/Caddyfile` 3. Run the [caddy ansible playbook](/active/container_caddy/caddy.md#install-caddy) ## Logs ```bash # Follow remote connections podman logs -f caddy | grep -e '^{' | jq -c '.request | {remote_ip,host}' # Filter out noisy hosts podman logs -f caddy | grep -e '^{' | jq -c '.request | {remote_ip,host} | select(.host != "gitea.reeseapps.com")' # Focus on user agents podman logs -f caddy | grep -e '^{' | jq -c ' { "User-Agent": .request.headers["User-Agent"], remote_ip: .request.remote_ip, host: .request.host, status: .status } ' ``` ## Caddy WAF 1. Copy the rules.json to `/etc/caddy/rules.json` 2. Update the Caddyfile to something like this: ```Caddyfile gitea.reeseapps.com:443 { log { output stdout format json { message_key msg # Key for the log message level_key severity # Key for the log level time_key timestamp # Key for the timestamp name_key logger # Key for the logger name caller_key function # Key for the caller information stacktrace_key stack # Key for error stacktraces time_format "2006-01-02 15:04:05 MST" # RFC3339-like format time_local # Use local timezone duration_format "ms" # Show durations in milliseconds level_format "upper" # Uppercase log levels } } route { waf { metrics_endpoint /waf_metrics rule_file rules.json } @wafmetrics { path /waf_metrics } handle @wafmetrics { } # empty → let the WAF serve the metrics handle { reverse_proxy gitea.reeselink.com:3000 } } } ```