1. Caddy version (caddy version
):
2.4.3
2. How I run Caddy:
a. System environment:
Ubuntu, installed via Ansible (GitHub - caddy-ansible/caddy-ansible: Ansible role for installing and configuring the Caddy web server), running via systemd
b. Command:
sudo systemctl start caddy
c. Service/unit/compose file:
;
; Ansible managed
;
; source: https://github.com/mholt/caddy/blob/master/dist/init/linux-systemd/caddy.service
; version: 6be0386
; changes: Set variables via Ansible
[Unit]
Description=Caddy HTTP/2 web server
Documentation=https://caddyserver.com/docs
After=network-online.target
[Service]
Restart=on-failure
StartLimitInterval=86400
StartLimitBurst=5
; User and group the process will run as.
User=www-data
Group=www-data
; Letsencrypt-issued certificates will be written to this directory.
Environment=CADDYPATH=/etc/ssl/caddy
ExecStart="/usr/local/bin/caddy" run --environ --config "/home/fuzzy/caddy/Caddyfile"
ExecReload="/usr/local/bin/caddy" reload --config "/home/fuzzy/caddy/Caddyfile"
; Limit the number of file descriptors; see `man systemd.exec` for more limit settings.
LimitNOFILE=1048576
; Use private /tmp and /var/tmp, which are discarded after caddy stops.
PrivateTmp=true
; Use a minimal /dev
PrivateDevices=true
; Hide /home, /root, and /run/user. Nobody will steal your SSH-keys.
ProtectHome=false
; Make /usr, /boot, /etc and possibly some more folders read-only.
ProtectSystem=full
; … except /etc/ssl/caddy, because we want Letsencrypt-certificates there.
; This merely retains r/w access rights, it does not add any new. Must still be writable on the host!
ReadWriteDirectories=/etc/ssl/caddy /var/log/caddy
; The following additional security directives only work with systemd v229 or later.
; They further retrict privileges that can be gained by caddy.
; Note that you may have to add capabilities required by any plugins in use.
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_BIND_SERVICE
NoNewPrivileges=true
[Install]
WantedBy=multi-user.target
d. My complete Caddyfile or JSON config:
Caddyfile #1
{
log {
output file /home/{{main_username}}/caddy/access.log {
roll_size 100mb
roll_keep 5
roll_keep_for 168h
}
}
email {{secret_email}}
}
(proxy_options) {
header X-Real-IP {remote_host}
header X-Forwarded-Proto {scheme}
}
(personal_headers) {
header {
Strict-Transport-Security "max-age=31536000; includeSubdomains"
X-XSS-Protection "1; mode=block"
X-Content-Type-Options "nosniff"
Referrer-Policy "same-origin"
Content-Security-Policy "frame-ancestors {{secret_personal_url}} *.{{secret_personal_url}}"
-Server
Permissions-Policy "geolocation=(self {{secret_personal_url}} *.{{secret_personal_url}}), microphone=()"
}
}
(no_robots) {
respond /robots.txt 200 {
body "User-agent: *
Disallow: /"
}
}
*.{{secret_personal_url}} {
tls /home/{{main_username}}/lego/certificates/_.{{secret_personal_url}}.crt /home/{{main_username}}/lego/certificates/_.{{secret_personal_url}}.key {
}
@smarthome host smarthome.{{secret_personal_url}}
handle @smarthome {
reverse_proxy 10.10.10.10:5315
import proxy_options
import personal_headers
import no_robots
}
@dashboard host dashboard.{{secret_personal_url}}
handle @dashboard {
reverse_proxy 10.10.10.10:5100
import proxy_options
import personal_headers
import no_robots
}
@photos host photos.{{secret_personal_url}}
handle @photos {
reverse_proxy 10.10.10.10:8000
import proxy_options
import personal_headers
import no_robots
}
@wallabag host wallabag.{{secret_personal_url}}
handle @wallabag {
reverse_proxy 10.10.10.10:300
import proxy_options
import personal_headers
import no_robots
}
# Fallback for otherwise unhandled domains
handle {
redir {{secret_redirect}}
}
}
Caddyfile #2
{
log {
output file /home/{{main_username}}/caddy/access.log {
roll_size 100mb
roll_keep 5
roll_keep_for 168h
}
}
email {{secret_email}}
auto_https off
}
(proxy_options) {
header X-Real-IP {remote_host}
header X-Forwarded-Proto {scheme}
}
(headers) {
header {
Strict-Transport-Security "max-age=31536000; includeSubdomains"
X-XSS-Protection "1; mode=block"
X-Content-Type-Options "nosniff"
Referrer-Policy "same-origin"
Content-Security-Policy "frame-ancestors {{secret_personal_url}} *.{{secret_personal_url}}"
-Server
Permissions-Policy "geolocation=(self {{secret_personal_url}} *.{{secret_personal_url}}), microphone=()"
}
}
:5315 {
reverse_proxy http://192.168.30.12:5315
import headers
import proxy_options
}
:5100 {
reverse_proxy http://192.168.30.13:5100
import headers
import proxy_options
}
:8000 {
reverse_proxy http://192.168.30.11:8000
import headers
import proxy_options
}
:300 {
reverse_proxy http://192.168.30.13:300
import headers
import proxy_options
}
3. The problem I’m having:
Bit of a long leadup but bear with me. I’m trying to create a set up where all my traffic is routed through a VPS and then through a Wireguard tunnel to my homelab. Goal is to avoid punching holes in my home firewall (including 80/443). The VPS runs Caddy and is the first Caddyfile above. I then would have a server running a second Caddy instance sitting in a DMZ to route the traffic to the appropriate server. I run a bunch of things in separated LXC/VMs. Here’s an image that shows what I’m doing:
The issue is I also want to run fail2ban on the app servers to ban any bots/malicious actors. I may use a cloud firewall from my VPS provider to make sure the firewall blocks the traffic at the source (I’ll figure that part out separately). My issue is that right now is that in the logs for some apps the IP address is either the IP address of the Caddy Server #2 (ie 192.168.10.5) or is the Wireguard ip address of Caddy Server #1 (10.10.10.10). Not sure if it’s an issue with the app or what, because when I look at the Caddy log the X-Forward-For
IP address is the IP address i’d want to ban (ie in this case my home IP as that’s what I’m testing from).
Perhaps the solution here is to rely on the Caddy log for the IP address to ban? I have a link to a thread down below that mentions looking into that from March but I don’t see any follow up and the thread is locked. Or is there a header/something I’ve misconfigured above that would lead to the “wrong” IP address showing up in the app logs? Would the “real ip” plugin help here?
4. Error messages and/or full log output:
Not really relevant I don’t think.
5. What I already tried:
You can see in my Caddyfile I’ve tried to pass through any relevant headers with IP addresses, but sometimes it’s still the wrong IP address seen.