Skip to content

Reverse Proxy

Caddy

Dockerhub - caddy

Modules

The Dockerfile uses xcaddy to build Caddy with the necessary modules.

caddy-dns/cloudflare
This module uses a Cloudflare token to neogiate the ACME challenge with Let's Encrypt.
mholt/caddy-dynamicdns
This module uses a Cloudflare token to update the the DNS entry for the root domain. This is only necessary if Cloudflare is forwarding traffic directly to your network because your public IP might change every once in a while. Using a Cloudflare tunnel obviates the need for this because the connection is established to Cloudflare by the tunnel.
Dockerfile with modules
Dockerfile
FROM caddy:2-builder AS builder

RUN xcaddy build \
    --with github.com/caddy-dns/cloudflare \
    --with github.com/mholt/caddy-dynamicdns

FROM caddy:2

RUN apk add --no-cache ca-certificates curl jq openssl
COPY --from=builder /usr/bin/caddy /usr/bin/caddy

Config

ACME DNS-01 Challenge

Uses the acme-dns and acme-ca global options

Global options block
{
    email {env.EMAIL}
    acme_dns cloudflare {env.CLOUDFLARE_API_TOKEN}
    acme_ca https://acme-v02.api.letsencrypt.org/directory
    # acme_ca https://acme-staging-v02.api.letsencrypt.org/directory
    log {
        output stdout
        format json
        level DEBUG
    }
}

Use the staging endpoint for development to avoid the rate limit

Security Headers

These additional headers increase security. Check if they're working with this security check.

Option block
(security_headers) {
    header {
        # Enable HSTS with 1 year max-age and include subdomains
        Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"

        # Prevent MIME type sniffing
        X-Content-Type-Options "nosniff"

        # Prevent clickjacking attacks
        X-Frame-Options "SAMEORIGIN"

        # Disable server tokens
        -Server

        # Control which features and APIs can be used
        Permissions-Policy "geolocation=(), microphone=(), camera=()"

        # Relaxed CSP - allows inline scripts/styles and websockets for Proxmox compatibility
        Content-Security-Policy "default-src 'self'; base-uri 'self'; object-src 'self'; script-src 'self' 'unsafe-eval' 'unsafe-inline'; style-src 'self' 'unsafe-inline' data: https://fonts.googleapis.com; img-src 'self' data: https:; font-src 'self' data: https://fonts.gstatic.com; connect-src 'self' wss:; frame-ancestors 'self'; form-action 'self'; upgrade-insecure-requests"
    }
}

If this endpoint is getting proxied by cloudflare, then these need to be applied with Response Header Transform Rules in the web dashboard.

Insecure Reverse Proxy

Some endpoints, such as Proxmox's web UI, use their own, self-signed certs for HTTPS. This function disables the TLS verify check for a proxied connection, which is essentially equivalent to proceeding through the browser warning. However, this allows you to specify what server name you expect on the cert provided.

These endpoints should never be exposed to the internet

Function defintion

Example usage
proxmox.john-stream.com {
    import reverse_proxy_insecure 192.168.1.130:8006
}
Slow Cloudflare DNS

Cloudflare sometimes(?) takes forever to propagate the TXT records for the DNS-01 challenge, so it times out. This block can be used to slow down the checks Caddy does, which allows it to succeed sometimes if it's having trouble. Unfortunately, these settings don't seem to be exposed as global options, so this block has to be imported wherever it's needed.

Option block
(slow_cloudflare_tls) {
    tls {
        dns cloudflare {env.CLOUDFLARE_API_TOKEN}
        propagation_timeout 1h
        propagation_delay 30m
    }
}
Example usage
appdaemon.john-stream.com {
    import slow_cloudflare_tls
    reverse_proxy 192.168.1.242:5050
}
The global acme_dns option has to be commented out for this to work.
Tunnel Route

This blocks modularizes only allowing traffic to come from the cloudflared container.

Option block
# Allow traffic only from cloudflared tunnel container
(allow_tunnel) {
    @not_tunnel not remote_ip 172.18.0.0/16
    respond @not_tunnel 403
}

# Tunnel route - only accepts traffic from cloudflared, includes security headers
(tunnel_route) {
    import allow_tunnel
    reverse_proxy {args[0]}
}
Split Horizon

The split horizon DNS is established by this block

Option block
# Combined HTTPS and HTTP (tunnel) routes for a service. This is only used for services that are exposed both internally and externally.
(split_horizon) {
    {args[0]}.john-stream.com, {args[0]}.john-stream.com:80 {
        @http protocol http
        handle @http {
            # Only traffic from the cloudflared tunnel can use HTTP
            import tunnel_route {args[1]}
        }

        @https protocol https
        handle @https {
            # Everything else must come from a local network and use HTTPS
            import local_route {args[1]}
        }
    }
}

Reference

Deploying Web Applications Quicker and Easier with Caddy 2

How to use DNS provider modules in Caddy 2

NGINX

Nginx (pronounced "engine x", stylized as NGINX or nginx) is a web server that can also be used as a reverse proxy, load balancer, mail proxy and HTTP cache.

Reverse Proxy

DockerHub - nginx

Default config location: /etc/nginx/nginx.conf

Serving Static Content

This feature is used to host the built document site for the final stage of its Dockerfile.

CoreDNS

This Corefile sets up the following behavior:

  • Local john-stream.com zone
    • Defined by the zone file
    • Uses fallthrough to forward failed requests to Cloudflare
  • Reverse DNS for certain IPs
  • DNS-over-TLS (DoT) communication with Cloudflare
Example Corefile
Corefile
# CoreDNS Configuration for Split Horizon DNS
# john-stream.com - Internal resolution with external fallback

# Internal zone for john-stream.com
john-stream.com {
    # Load internal zone file for local resolution
    file /etc/coredns/john-stream.com.zone john-stream.com {
        fallthrough
    }
    # Forward to Cloudflare if record not found in zone file
    forward . tls://1.1.1.1 tls://1.0.0.1 {
        tls_servername cloudflare-dns.com
    }
    log
    errors
}

# Handle reverse DNS for internal IPs if needed
# Adjust this range based on your internal network
168.192.in-addr.arpa {
    file /etc/coredns/db.192.168
    log
    errors
}

# Default: All other queries go to Cloudflare DNS
. {
    forward . tls://1.1.1.1 tls://1.0.0.1 {
        tls_servername cloudflare-dns.com
    }
    log
    errors
    cache 30
}
Example zone file
john-stream.com.zone
$ORIGIN john-stream.com.
$TTL 3600

; CoreDNS Local Resolution Only - NOT an authoritative nameserver
; External queries should go to Cloudflare nameservers
; This file only provides internal IP overrides for split-horizon DNS

; SOA Record (required by CoreDNS, but not used as authoritative)
@       IN      SOA     localhost. admin.localhost. (
                        2025110402  ; Serial (YYYYMMDDNN)
                        7200        ; Refresh
                        3600        ; Retry
                        1209600     ; Expire
                        3600        ; Minimum TTL
)

; Main server/host
@                     IN  A             192.168.1.150
hermes      3600      IN  A             192.168.1.150
panoptes    3600      IN  A             192.168.1.107

asdf        3600      IN  CNAME         tunnel
appdaemon   3600      IN  CNAME         hermes
cron        3600      IN  CNAME         hermes
docs        3600      IN  CNAME         hermes
gitea       3600      IN  CNAME         hermes
grafana     3600    IN  A           192.168.1.177

Reverse DNS Lookup

Example reverse lookup file
db.192.168
$TTL    86400
@       IN      SOA     john-stream.com. admin.john-stream.com. (
                        2025112301 ; Serial (YYYYMMDDnn)
                        3600       ; Refresh
                        1800       ; Retry
                        604800     ; Expire
                        86400 )    ; Minimum

        IN      NS      john-stream.com.

; PTR Records for 192.168.0.0/16
; Format: <last_octet> IN PTR <hostname>.
; Example:
; 1.1   IN PTR host1.john-stream.com.
; 10.1  IN PTR host2.john-stream.com.

100.1   IN PTR nas.john-stream.com.
150.1   IN PTR hermes.john-stream.com.
177.1   IN PTR grafana.john-stream.com.

The period at the end of the entries is actually important.

Test reverse lookup
$ dig +short -x 192.168.1.150
hermes.john-stream.com.