Update Caddy guide: upstream TLS, NetBird, Tor, Yggdrasil
Deploy to LXC / deploy (push) Successful in 19s
Deploy to LXC / deploy (push) Successful in 19s
Rewritten for the actual architecture: separate upstream Caddy handles TLS for public domain, LXC Caddy only does HTTP. Added NetBird interface binding, explicit per-interface blocks, upstream Caddy config snippet. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+119
-78
@@ -1,27 +1,42 @@
|
|||||||
# Caddy Reverse Proxy Setup
|
# Caddy Reverse Proxy Setup
|
||||||
|
|
||||||
Caddy sits in front of the SvelteKit app and handles all access methods:
|
The LXC runs a local Caddy that accepts traffic from all networks and proxies
|
||||||
- Internal network (LAN/Tailscale)
|
to the SvelteKit app. TLS termination for the public domain happens on a
|
||||||
- Tor hidden service
|
separate upstream Caddy — this instance only handles plain HTTP.
|
||||||
- Yggdrasil mesh network
|
|
||||||
- Public domain with automatic HTTPS
|
|
||||||
|
|
||||||
## 1. Install Caddy
|
## Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────┐
|
||||||
|
Internet ──► Upstream Caddy (TLS) ──────┤ │
|
||||||
|
collection.newedge.house │ │
|
||||||
|
│ LXC: Caddy (:80) ──► :3000 │
|
||||||
|
NetBird ──► nb-ip:80 ──────────────────┤ (SvelteKit app) │
|
||||||
|
│ │
|
||||||
|
Yggdrasil──► [200:...]:80 ──────────────┤ │
|
||||||
|
│ │
|
||||||
|
Tor ──► .onion ──► tor ──► :8880 ──┤ │
|
||||||
|
└─────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
All routes inject `Host: collection.newedge.house` so SvelteKit CSRF passes.
|
||||||
|
|
||||||
|
## 1. Install Caddy and Tor
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
# Caddy
|
||||||
apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
|
apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
|
||||||
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
|
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
|
||||||
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | tee /etc/apt/sources.list.d/caddy-stable.list
|
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | tee /etc/apt/sources.list.d/caddy-stable.list
|
||||||
apt update
|
apt update
|
||||||
apt install caddy
|
apt install caddy
|
||||||
```
|
|
||||||
|
|
||||||
## 2. Install Tor
|
# Tor
|
||||||
|
|
||||||
```bash
|
|
||||||
apt install tor
|
apt install tor
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 2. Configure Tor hidden service
|
||||||
|
|
||||||
Edit `/etc/tor/torrc`:
|
Edit `/etc/tor/torrc`:
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -29,19 +44,34 @@ HiddenServiceDir /var/lib/tor/bflr/
|
|||||||
HiddenServicePort 80 127.0.0.1:8880
|
HiddenServicePort 80 127.0.0.1:8880
|
||||||
```
|
```
|
||||||
|
|
||||||
Restart and get your .onion address:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
systemctl restart tor
|
systemctl restart tor
|
||||||
cat /var/lib/tor/bflr/hostname
|
cat /var/lib/tor/bflr/hostname
|
||||||
|
# → your .onion address
|
||||||
```
|
```
|
||||||
|
|
||||||
## 3. Configure Caddy
|
## 3. Get your interface addresses
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Yggdrasil IPv6
|
||||||
|
yggdrasilctl getSelf
|
||||||
|
# → 200:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx
|
||||||
|
|
||||||
|
# NetBird IP
|
||||||
|
ip addr show wt0
|
||||||
|
# → 100.x.x.x (or whatever your NetBird interface is called)
|
||||||
|
|
||||||
|
# LAN IP
|
||||||
|
ip addr show eth0
|
||||||
|
# → 192.168.x.x
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. Caddyfile
|
||||||
|
|
||||||
Edit `/etc/caddy/Caddyfile`:
|
Edit `/etc/caddy/Caddyfile`:
|
||||||
|
|
||||||
```caddy
|
```caddy
|
||||||
# ─── Shared config ───────────────────────────────────────────────────
|
# ─── Shared snippets ────────────────────────────────────────────────
|
||||||
|
|
||||||
(proxy) {
|
(proxy) {
|
||||||
reverse_proxy 127.0.0.1:3000 {
|
reverse_proxy 127.0.0.1:3000 {
|
||||||
@@ -59,25 +89,14 @@ Edit `/etc/caddy/Caddyfile`:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# ─── Public domain (automatic HTTPS via Let's Encrypt) ───────────────
|
# ─── Public traffic (HTTP from upstream Caddy, TLS already terminated) ─
|
||||||
|
|
||||||
collection.newedge.house {
|
|
||||||
import common
|
|
||||||
reverse_proxy 127.0.0.1:3000 {
|
|
||||||
header_up X-Real-IP {remote_host}
|
|
||||||
header_up X-Forwarded-For {remote_host}
|
|
||||||
header_up X-Forwarded-Proto https
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# ─── Internal / LAN access (HTTP on port 80) ─────────────────────────
|
|
||||||
|
|
||||||
:80 {
|
:80 {
|
||||||
import common
|
import common
|
||||||
import proxy
|
import proxy
|
||||||
}
|
}
|
||||||
|
|
||||||
# ─── Tor hidden service (HTTP on port 8880, Tor connects here) ───────
|
# ─── Tor hidden service (Tor connects to localhost:8880) ─────────────
|
||||||
|
|
||||||
:8880 {
|
:8880 {
|
||||||
import common
|
import common
|
||||||
@@ -85,56 +104,75 @@ collection.newedge.house {
|
|||||||
bind 127.0.0.1
|
bind 127.0.0.1
|
||||||
}
|
}
|
||||||
|
|
||||||
# ─── Yggdrasil (HTTP on port 80, bind to Yggdrasil IPv6) ─────────────
|
# ─── Yggdrasil (bind to Yggdrasil IPv6 only) ────────────────────────
|
||||||
# Replace with your actual Yggdrasil address from: yggdrasilctl getSelf
|
# Replace with your address from: yggdrasilctl getSelf
|
||||||
|
|
||||||
http://[200:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx]:80 {
|
http://[200:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx]:80 {
|
||||||
import common
|
import common
|
||||||
import proxy
|
import proxy
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ─── NetBird (bind to NetBird interface IP only) ─────────────────────
|
||||||
|
# Replace with your address from: ip addr show wt0
|
||||||
|
|
||||||
|
http://100.x.x.x:80 {
|
||||||
|
import common
|
||||||
|
import proxy
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**Important:** Replace `200:xxxx:...` with your actual Yggdrasil IPv6 address.
|
**Replace:**
|
||||||
|
- `200:xxxx:...` with your Yggdrasil address
|
||||||
|
- `100.x.x.x` with your NetBird IP
|
||||||
|
|
||||||
The `(proxy)` snippet sets `Host: collection.newedge.house` on all non-public routes so SvelteKit's CSRF check passes regardless of how you access the app.
|
**Note:** The `:80` block catches all traffic including from the upstream Caddy.
|
||||||
|
The Yggdrasil and NetBird blocks are explicit bindings so Caddy listens on
|
||||||
|
those interfaces. If they conflict with `:80`, remove the `:80` block and
|
||||||
|
add explicit LAN binding instead:
|
||||||
|
|
||||||
## 4. Update the SvelteKit service
|
```caddy
|
||||||
|
# If you need separate LAN binding instead of catch-all :80
|
||||||
|
http://192.168.x.x:80 {
|
||||||
|
import common
|
||||||
|
import proxy
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
Change the app to only listen on localhost since Caddy handles external access:
|
## 5. Upstream Caddy (the TLS termination server)
|
||||||
|
|
||||||
|
On your upstream Caddy that handles public TLS, add:
|
||||||
|
|
||||||
|
```caddy
|
||||||
|
collection.newedge.house {
|
||||||
|
reverse_proxy LXC_IP:80 {
|
||||||
|
header_up X-Forwarded-Proto https
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Replace `LXC_IP` with the LXC's LAN or Tailscale/NetBird IP as seen from
|
||||||
|
the upstream server.
|
||||||
|
|
||||||
|
## 6. App config
|
||||||
|
|
||||||
|
The app only listens on localhost:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
nano /home/bflr/buildfor_life_repair/.env
|
# /home/bflr/buildfor_life_repair/.env
|
||||||
```
|
|
||||||
|
|
||||||
```
|
|
||||||
HOST=127.0.0.1
|
HOST=127.0.0.1
|
||||||
PORT=3000
|
PORT=3000
|
||||||
|
ORIGIN=https://collection.newedge.house
|
||||||
|
BASE_URL=https://collection.newedge.house
|
||||||
```
|
```
|
||||||
|
|
||||||
Update systemd if HOST is set there too:
|
systemd service:
|
||||||
|
|
||||||
```bash
|
|
||||||
nano /etc/systemd/system/bflr.service
|
|
||||||
```
|
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
Environment=HOST=127.0.0.1
|
Environment=HOST=127.0.0.1
|
||||||
|
Environment=PORT=3000
|
||||||
```
|
```
|
||||||
|
|
||||||
## 5. Firewall
|
## 7. Start everything
|
||||||
|
|
||||||
Only Caddy needs external access:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Allow HTTP/HTTPS from anywhere
|
|
||||||
ufw allow 80/tcp
|
|
||||||
ufw allow 443/tcp
|
|
||||||
|
|
||||||
# Block direct app access from outside
|
|
||||||
ufw deny 3000/tcp
|
|
||||||
```
|
|
||||||
|
|
||||||
## 6. Start everything
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
systemctl daemon-reload
|
systemctl daemon-reload
|
||||||
@@ -143,48 +181,51 @@ systemctl enable --now caddy
|
|||||||
systemctl restart tor
|
systemctl restart tor
|
||||||
```
|
```
|
||||||
|
|
||||||
## 7. Verify
|
## 8. Verify each path
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Public domain
|
# Via upstream (public domain)
|
||||||
curl -s -o /dev/null -w "%{http_code}" https://collection.newedge.house/login
|
curl -s -o /dev/null -w "%{http_code}" https://collection.newedge.house/login
|
||||||
|
|
||||||
# Internal
|
# Direct LAN
|
||||||
curl -s -o /dev/null -w "%{http_code}" http://localhost/login
|
curl -s -o /dev/null -w "%{http_code}" http://LXC_LAN_IP/login
|
||||||
|
|
||||||
|
# NetBird
|
||||||
|
curl -s -o /dev/null -w "%{http_code}" http://100.x.x.x/login
|
||||||
|
|
||||||
|
# Yggdrasil (from a peer)
|
||||||
|
curl -s -o /dev/null -w "%{http_code}" http://[200:xxxx:...]/login
|
||||||
|
|
||||||
# Tor (from Tor Browser)
|
# Tor (from Tor Browser)
|
||||||
# http://your-onion-address.onion/login
|
# http://your-onion-address.onion/login
|
||||||
|
|
||||||
# Yggdrasil (from a Yggdrasil peer)
|
# Local test
|
||||||
# http://[200:xxxx:...]/login
|
curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:3000/login
|
||||||
```
|
```
|
||||||
|
|
||||||
## How it works
|
|
||||||
|
|
||||||
```
|
|
||||||
Internet ──► collection.newedge.house:443 ──► Caddy ──► :3000 (app)
|
|
||||||
LAN ──► server-ip:80 ──► Caddy ──► :3000 (app)
|
|
||||||
Tor ──► .onion:80 ──► tor ──► :8880 ──► Caddy ──► :3000 (app)
|
|
||||||
Yggdrasil──► [200:...]:80 ──► Caddy ──► :3000 (app)
|
|
||||||
```
|
|
||||||
|
|
||||||
All routes set `Host: collection.newedge.house` so SvelteKit CSRF passes.
|
|
||||||
Caddy handles TLS for the public domain automatically via Let's Encrypt.
|
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Check Caddy status
|
# Caddy
|
||||||
systemctl status caddy
|
systemctl status caddy
|
||||||
journalctl -u caddy -f
|
journalctl -u caddy -f
|
||||||
|
caddy validate --config /etc/caddy/Caddyfile
|
||||||
|
|
||||||
# Check Tor status
|
# Tor
|
||||||
systemctl status tor
|
systemctl status tor
|
||||||
cat /var/lib/tor/bflr/hostname
|
cat /var/lib/tor/bflr/hostname
|
||||||
|
|
||||||
# Check Yggdrasil address
|
# Yggdrasil
|
||||||
yggdrasilctl getSelf
|
yggdrasilctl getSelf
|
||||||
|
yggdrasilctl getPeers
|
||||||
|
|
||||||
# Test reverse proxy
|
# NetBird
|
||||||
|
netbird status
|
||||||
|
|
||||||
|
# App
|
||||||
|
systemctl status bflr
|
||||||
|
journalctl -u bflr -f
|
||||||
|
|
||||||
|
# Test CSRF manually
|
||||||
curl -H "Host: collection.newedge.house" http://127.0.0.1:3000/login
|
curl -H "Host: collection.newedge.house" http://127.0.0.1:3000/login
|
||||||
```
|
```
|
||||||
|
|||||||
Reference in New Issue
Block a user