name: caddy-https-reverse-proxy description: >- Deploy Caddy (no Docker) on a remote Linux host to expose a local HTTP backend as trusted HTTPS, prioritizing first-party domain + Let's Encrypt (HTTP-01), with DuckDNS DNS-01 fallback, low-bandwidth binary transfer fallback, verification, and rollback.
Caddy HTTPS Reverse Proxy (Domain-First)
Use this skill to deploy or repair a production-style HTTPS reverse proxy on
a remote Linux server where backend API is only HTTP (for example
127.0.0.1:39080).
Default policy:
- Prefer user-owned domain first (for example
api.example.com). - Use DuckDNS only as fallback when no usable own-domain is available.
When To Use
Use this skill when the user asks to:
- Add trusted HTTPS in front of an existing HTTP backend.
- Configure Caddy without Docker.
- Use their own domain with Let's Encrypt automatic certificate issuance.
- Fallback to DuckDNS DNS-01 when own-domain path is unavailable.
- Prepare a backend endpoint consumable by browser frontends (for example GitHub Pages).
Required User Inputs
Collect and confirm these values before execution:
ssh_host: target SSH host (IP or domain).ssh_user: remote SSH user.domain: target HTTPS domain (prefer user-owned domain, for exampleapi.ackingliu.top).backend_upstream: backend HTTP upstream (default127.0.0.1:39080).contact_email: ACME email (use a real mailbox when possible).
Optional but useful:
public_ip: expected public IP of target server.ssh_port: default22.use_local_download_upload:truewhen remote network is unstable.disable_http3:trueto force onlyh1/h2.
DuckDNS fallback only:
duckdns_domain: DuckDNS hostname (for examplesfapi.duckdns.org).duckdns_token: DuckDNS token for DNS challenge.
Preconditions
domainresolves to the target public IP.- Inbound
80/tcpand443/tcpare reachable. - Backend service will bind loopback (
127.0.0.1) instead of0.0.0.0. - Remote user has
sudoprivilege.
Security Rules
- Never hardcode token directly in
Caddyfile. - If DuckDNS mode is used, store token in root-only env file:
/etc/caddy/caddy.envwith mode600.
- Do not run Caddy with
--environin systemd ExecStart, to avoid leaking env vars into logs. - Do not expose backend upstream port publicly.
- If switching from DuckDNS to own domain, remove stale DuckDNS env/drop-in.
Deployment Workflow
Step 1: Select deployment mode
Choose one mode before execution:
domain-first(default and preferred):- Use user-owned domain.
- Use standard Caddy + Let's Encrypt (
http-01). - No DNS plugin and no token file needed.
duckdns-fallback:- Use only when user has no usable own domain or explicitly requests it.
- Use Let's Encrypt DNS-01 via
dns.providers.duckdns.
Step 2: Preflight checks
Run on remote:
hostname && whoami && datesudo -n true(or explicit sudo check)dig +short A <domain>sudo ss -lntup | grep -E '(:80|:443|:39080|:22)' || true
If public_ip is provided and DNS does not match it, stop and ask user to fix
DNS first.
Step 3: Install baseline packages
On remote:
sudo apt-get update -ysudo apt-get install -y caddy curl ca-certificates
Step 4: Configure Caddyfile (domain-first preferred)
Create /etc/caddy/Caddyfile:
{
email admin@example.com
servers {
protocols h1 h2
}
}
api.example.com {
@health path /_caddy_health
respond @health "ok" 200
reverse_proxy 127.0.0.1:39080
}
Replace:
- email with
contact_email - site address with
domain - upstream with
backend_upstream - if
disable_http3isfalse, you may removeservers { protocols h1 h2 }
Step 5: DuckDNS fallback path (only when needed)
Requirement: module dns.providers.duckdns must exist.
Preferred download URL:
https://caddyserver.com/api/download?os=linux&arch=amd64&p=github.com/caddy-dns/duckdns
Two supported methods:
- Remote direct download:
- download to
/tmp/caddy-custom
- download to
- Local download + upload fallback (recommended when remote bandwidth is poor):
- download locally
scpupload to/tmp/caddy-customon remote
Then on remote:
chmod +x /tmp/caddy-custom/tmp/caddy-custom list-modules | grep dns.providers.duckdns- backup existing binary:
sudo cp /usr/bin/caddy /usr/bin/caddy.bak.<timestamp>
- replace:
sudo install -m 755 /tmp/caddy-custom /usr/bin/caddy
- verify:
caddy versioncaddy list-modules | grep dns.providers.duckdns
Then configure token and systemd environment: Create env file:
/etc/caddy/caddy.env- content:
DUCKDNS_TOKEN=<duckdns_token>
- permission:
chmod 600 /etc/caddy/caddy.envchown root:root /etc/caddy/caddy.env
Create/overwrite drop-in:
/etc/systemd/system/caddy.service.d/env.conf- content:
[Service]EnvironmentFile=/etc/caddy/caddy.envExecStart=ExecStart=/usr/bin/caddy run --config /etc/caddy/Caddyfile
DuckDNS mode Caddyfile template:
{
email admin@example.com
servers {
protocols h1 h2
}
}
sfapi.duckdns.org {
tls {
dns duckdns {env.DUCKDNS_TOKEN}
resolvers 1.1.1.1 8.8.8.8
}
@health path /_caddy_health
respond @health "ok" 200
reverse_proxy 127.0.0.1:39080
}
Replace:
- email with
contact_email - site address with
domain - upstream with
backend_upstream
Step 6: Validate and restart
On remote:
sudo caddy validate --config /etc/caddy/Caddyfilesudo systemctl daemon-reloadsudo systemctl restart caddysudo systemctl status caddy --no-pager -lsudo journalctl -u caddy -n 120 --no-pager -l
Expected success signal:
- log contains challenge flow matching selected mode:
- domain-first: usually
challenge_type":"http-01"(ortls-alpn-01) - duckdns-fallback:
challenge_type":"dns-01"
- domain-first: usually
- log contains certificate obtained message
- listeners include
:80and:443
Step 7: End-to-end verification
Verify from remote and local client:
curl -I http://<domain>/_caddy_health->308redirect to HTTPScurl -I https://<domain>/_caddy_health->200- certificate check:
echo | openssl s_client -connect <domain>:443 -servername <domain> 2>/dev/null | openssl x509 -noout -issuer -subject -dates
If backend is not started yet, /_caddy_health must still return 200 by Caddy.
Step 8: Cleanup stale fallback config (when domain-first is selected)
If machine previously used DuckDNS mode:
- remove
/etc/systemd/system/caddy.service.d/env.confif present. - remove
/etc/caddy/caddy.envif present. sudo systemctl daemon-reloadsudo systemctl restart caddy
Firewall and Cloud-Network Notes
If certificate issuance fails:
- In domain-first mode, confirm inbound
80/443are reachable from internet. - Check cloud security group allows inbound 80/443 from
0.0.0.0/0. - Check host firewall order so explicit allow for 80/443 is effective.
- Recheck domain A/AAAA record accuracy.
- If network path for HTTP challenge is unstable, switch to DuckDNS DNS-01.
Common Failure Patterns
- Browser still warns:
- stale cert; verify with
openssl s_clientand clear CDN/proxy assumptions.
- stale cert; verify with
502from Caddy:- backend not running on configured
backend_upstream.
- backend not running on configured
dns.providers.duckdnsmissing in DuckDNS mode:- wrong Caddy binary, reinstall plugin-enabled binary.
- DNS challenge fails in DuckDNS mode:
- invalid token, wrong domain, or DNS propagation delay.
- TLS internal errors from some clients:
- verify client-side proxy/VPN interference and compare direct network path.
Rollback
If deployment must be reverted:
- restore old binary:
sudo cp /usr/bin/caddy.bak.<timestamp> /usr/bin/caddy
- restore prior
/etc/caddy/Caddyfilefrom backup. - remove drop-in/env only if needed:
/etc/systemd/system/caddy.service.d/env.conf/etc/caddy/caddy.env
sudo systemctl daemon-reload && sudo systemctl restart caddy
Output Contract
When finishing this skill, report:
- selected mode:
domain-firstorduckdns-fallback. - final public HTTPS URL.
- cert issuer + validity dates.
- upstream target (
backend_upstream) configured. - Caddy version and whether DuckDNS module is required/installed.
- files created/changed:
/etc/caddy/Caddyfile/etc/caddy/caddy.env(DuckDNS mode only)/etc/systemd/system/caddy.service.d/env.conf(DuckDNS mode only)/usr/bin/caddy(plus backup path)
Docker Compose Architect
DevOps
Designs optimized Docker Compose configurations.
Incident Postmortem Writer
DevOps
Writes structured and blameless incident postmortem reports.
Runbook Creator
DevOps
Creates clear operational runbooks for common DevOps procedures.