claude-home/server-configs/caddy-migration/MIGRATION.md
Cal Corum 4b7eca8a46
All checks were successful
Reindex Knowledge Base / reindex (push) Successful in 3s
docs: add YAML frontmatter to all 151 markdown files
Adds title, description, type, domain, and tags frontmatter to every
doc for improved KB semantic search. The description field is prepended
to every search chunk, and domain/type/tags enable filtered queries.

Type values: context, guide, runbook, reference, troubleshooting
Domain values match directory structure (networking, docker, etc.)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 09:00:44 -05:00

6.9 KiB

title description type domain tags
NPM to Caddy Migration Runbook Step-by-step operational runbook for migrating from Nginx Proxy Manager to Caddy on 10.10.0.16. Four phases: prepare (test on alternate ports), cutover (<2 min downtime), validate (24-48h monitoring), and cleanup. Includes rollback plan. runbook server-configs
caddy
nginx-proxy-manager
migration
reverse-proxy
dns
pihole
cloudflare

NPM to Caddy Migration Plan

Step-by-step guide to migrate from Nginx Proxy Manager to Caddy on 10.10.0.16.

Prerequisites

  • Cloudflare API token with Zone:DNS:Edit for manticorum.com
  • SSH access to pihole (10.10.0.16) and ubuntu-manticore (10.10.0.226)
  • Docker and docker compose installed on 10.10.0.16
  • Familiarity with current NPM proxy hosts (see README.md)

Phase 1: Prepare (no downtime, no changes to production)

1.1 Create Cloudflare API Token

  1. Go to https://dash.cloudflare.com/profile/api-tokens
  2. Create token with permissions:
    • Zone - DNS - Edit (scoped to manticorum.com)
  3. Save the token securely

1.2 Deploy Caddy Config to Host

# From workstation
scp -r server-configs/caddy-migration/ pihole:/home/cal/caddy/
ssh pihole "cp /home/cal/caddy/.env.example /home/cal/caddy/.env"

Edit .env on the host:

ssh pihole "nano /home/cal/caddy/.env"
# Set CF_API_TOKEN=<your token>

1.3 Build and Test Caddy (on alternate ports)

Temporarily modify docker-compose.yml to use non-conflicting ports:

ports:
  - "8080:80"
  - "8443:443"
  - "8443:443/udp"
ssh pihole "cd /home/cal/caddy && docker compose up -d --build"

Verify the container starts and the Cloudflare module is loaded:

ssh pihole "docker logs caddy 2>&1 | head -30"
ssh pihole "docker exec caddy caddy list-modules | grep cloudflare"

Verify config is valid:

ssh pihole "docker exec caddy caddy validate --config /etc/caddy/Caddyfile"

Test a proxy host directly (bypass DNS):

curl -k --resolve sbadev.manticorum.com:8443:10.10.0.16 https://sbadev.manticorum.com:8443/

1.4 Verify Cert Issuance

Check that Caddy successfully obtains a wildcard cert:

ssh pihole "docker logs caddy 2>&1 | grep -i 'certificate\|tls\|acme'"

You should see successful ACME DNS-01 challenge completion.

1.5 Stop Test Caddy

ssh pihole "cd /home/cal/caddy && docker compose down"

Revert docker-compose.yml ports back to 80/443.

Phase 2: Cutover (brief downtime)

Expected downtime: < 2 minutes (stop NPM, start Caddy, sync DNS).

2.1 Backup NPM

ssh pihole "cd ~/nginx-proxy-manager && tar czf ~/npm-backup-$(date +%Y%m%d).tar.gz data/ letsencrypt/"

2.2 Take a Snapshot

If 10.10.0.16 is a VM/LXC on Proxmox, take a snapshot first:

# From proxmox host (adjust VMID)
pct snapshot <VMID> pre-caddy-migration

2.3 Stop NPM

ssh pihole "cd ~/nginx-proxy-manager && docker compose down"

Ports 80, 443, and 81 are now free.

2.4 Start Caddy

ssh pihole "cd /home/cal/caddy && docker compose up -d"

2.5 Verify Services

Quick smoke test of key services:

# Test from workstation (DNS should already point to 10.10.0.16 via Pi-hole)
curl -sI https://git.manticorum.com | head -5
curl -sI https://n8n.manticorum.com | head -5
curl -sI https://jellyfin.manticorum.com | head -5
curl -sI https://foundry.manticorum.com | head -5
curl -sI https://status.manticorum.com | head -5

# Test internal-only access
curl -sI https://radarr.manticorum.com | head -5   # should work from local
curl -sI https://sonarr.manticorum.com | head -5

2.6 Update Pi-hole Sync

Deploy the new sync script:

ssh pihole "cp /home/cal/caddy/scripts/caddy-pihole-sync.sh /home/cal/scripts/"
ssh pihole "chmod +x /home/cal/scripts/caddy-pihole-sync.sh"

Test dry run:

ssh pihole "/home/cal/scripts/caddy-pihole-sync.sh --dry-run"

Run sync:

ssh pihole "/home/cal/scripts/caddy-pihole-sync.sh"

Update cron to use the new script:

ssh pihole "crontab -l | sed 's|npm-pihole-sync.sh|caddy-pihole-sync.sh|g' | crontab -"

Also update the CADDYFILE path variable in the script if the deployment path differs from /home/cal/caddy/Caddyfile.

Phase 3: Validate (next 24-48 hours)

3.1 Monitor Caddy Logs

ssh pihole "docker logs caddy -f"

Look for:

  • Successful TLS handshakes
  • No upstream connection errors
  • Cert renewal events (if timing aligns)

3.2 Check Uptime Kuma

Verify all monitored services at https://status.manticorum.com show UP.

3.3 Test WebSocket Services

These services use WebSockets and should be tested interactively:

  • Foundry VTT (foundry.manticorum.com) - open a game session
  • n8n (n8n.manticorum.com) - open workflow editor
  • Memos (memos.manticorum.com) - create/edit a memo
  • Termix (termix.manticorum.com) - open a terminal session

3.4 Test External Access

If any services are accessed via Cloudflare from outside:

  1. From a phone on cellular (not on home WiFi)
  2. Access public services and verify they load
  3. Access internal-only services and verify 403 response

3.5 Verify Access Restrictions

# From a machine NOT on 10.0.0.0/23 or 10.10.0.0/24:
curl -sI https://radarr.manticorum.com  # Should return 403
curl -sI https://sonarr.manticorum.com  # Should return 403

Phase 4: Cleanup

4.1 Remove NPM (after validation period)

# Keep backup, remove containers and images
ssh pihole "cd ~/nginx-proxy-manager && docker compose rm -f"
ssh pihole "docker image rm jc21/nginx-proxy-manager:latest"

4.2 Update Documentation

  • Update server-configs/networking/nginx-proxy-manager-pihole.md to reference Caddy
  • Update any Uptime Kuma monitors that check port 81 (NPM admin)
  • Update CONTEXT.md networking section

4.3 Free Port 81

Port 81 (NPM admin UI) is no longer needed. Caddy's admin API runs on localhost:2019 inside the container by default (not exposed).

Rollback Plan

If something goes wrong, rollback takes < 1 minute:

# Stop Caddy
ssh pihole "cd /home/cal/caddy && docker compose down"

# Restart NPM
ssh pihole "cd ~/nginx-proxy-manager && docker compose up -d"

# Revert cron to old sync script
ssh pihole "crontab -l | sed 's|caddy-pihole-sync.sh|npm-pihole-sync.sh|g' | crontab -"

Or restore from Proxmox snapshot:

pct rollback <VMID> pre-caddy-migration

Adding New Services After Migration

Edit the Caddyfile and reload - no web UI needed:

ssh pihole "nano /home/cal/caddy/Caddyfile"

# Add a new block:
# newservice.manticorum.com {
#     reverse_proxy 10.10.0.xxx:port {
#         import proxy_headers
#     }
# }

# Validate
ssh pihole "docker exec caddy caddy validate --config /etc/caddy/Caddyfile"

# Apply (zero downtime)
ssh pihole "docker exec caddy caddy reload --config /etc/caddy/Caddyfile"

# Sync DNS to Pi-holes
ssh pihole "/home/cal/scripts/caddy-pihole-sync.sh"