networking · diff

git:20260314.9629b35 to git:20260906.2a3604e

177 added, 414 removed. Audit A to A.

---
name: networking
description: >-
- Network infrastructure for self-hosted environments: VLANs, firewalls (nftables,
- OPNsense, pfSense), DNS (Pi-hole, AdGuard Home, split-horizon), reverse proxies
- (Caddy, Traefik, Nginx Proxy Manager), VPN (WireGuard, Tailscale), TLS/SSL
- certificate management, DHCP, and security hardening. Invoke when task involves
- any interaction with network configuration — designing, implementing, debugging,
- reviewing, or planning network architecture.
+ Write and review network configuration for self-hosted Linux infrastructure: what an nftables ruleset, a resolver
+ zone, a reverse-proxy site, a forward-auth chain, a WireGuard or Tailscale tunnel, or an IDS rule does once loaded,
+ where that differs from what the file reads. Not how a change is applied or verified.
+ when_to_use: >-
+ Invoke whenever network configuration is touched at all — writing or reviewing an nftables, firewalld, ufw, OPNsense,
+ or pfSense ruleset, a VLAN, bridge, route, or sysctl, an Unbound, Pi-hole, AdGuard Home, dnsmasq, or systemd-resolved
+ config, a Caddyfile, a Traefik or nginx config, an ACME or TLS setting, an Authelia or authentik integration, a
+ WireGuard, Tailscale, Headscale, or cloudflared config, or a Suricata, CrowdSec, or fail2ban setup. Also invoke on
+ the symptoms: a rule that loads clean and filters nothing, a published Docker port reachable through a denying
+ firewall, an internal name that resolves to its public address or to NXDOMAIN, a proxy that logs the CDN's address
+ or trusts a client-supplied identity header, a tunnel with a fresh handshake and no traffic, a banned IP that stays
+ connected, or an IDS that alerts and drops nothing. Covers what the artifact does; applying it and proving its
+ effect belongs to devops, container networking modes to containers, the hypervisor bridge to proxmox.
+ compatibility: Uses Claude Code frontmatter beyond the Agent Skills spec (when_to_use)
---
- # Networking
-
- Security is a non-negotiable default, not an optional add-on. Every network design decision must account for trust
- boundaries.
-
- ## References
-
- Extended configuration examples, comparison tables, and detailed patterns for the rules below live in
- `${CLAUDE_SKILL_DIR}/references/`.
-
- - `vlan-segmentation.md` — VLAN design, trunk/access ports, inter-VLAN policy, Layer 2 security: segment table, firewall
- rule matrix, hardware requirements, DHCP snooping, DAI, port security, L2 attack mitigation
- - `firewall-rules.md` — nftables syntax, OPNsense/pfSense hardening, IPv6 firewall rules: chain types/hooks/priorities,
- connection tracking, NAT, rate limiting, sets/maps, ICMPv6 policy, dual-stack rules
- - `dns-architecture.md` — Pi-hole, AdGuard Home, split-horizon, mDNS, Unbound, DoH/DoT: tool comparison, deployment
- patterns, Avahi reflector config, recursive vs authoritative, encrypted DNS, IPv6 DNS
- - `reverse-proxy.md` — Caddy, Traefik, Nginx Proxy Manager, Cloudflare tunnels: Caddyfile examples, Traefik Docker
- labels, decision matrix, snippet patterns, tunnel patterns, auth proxy integration
- - `vpn-tunnels.md` — WireGuard, Tailscale, Headscale, site-to-site, HA with OSPF: config examples, topology comparison,
- subnet router, hybrid WG+TS, HA failover with BIRD/OSPF
- - `tls-certificates.md` — Let's Encrypt, ACME, wildcard certs, acme.sh: challenge types, ACME client comparison,
- certificate storage patterns, TLS config
- - `security-hardening.md` — SSH, fail2ban, CrowdSec, IDS/IPS, monitoring, hardening: sshd_config, SSH CA, fail2ban vs
- CrowdSec, Suricata IDS, Prometheus stack, monitoring metrics, IPv6 hardening
- - `auth-proxies.md` — Authelia, Authentik, forward auth, SSO patterns: Authelia vs Authentik comparison, ForwardAuth
- with Traefik/Caddy, SSO/MFA patterns, deployment guidance
-
- ## VLAN Segmentation
-
- ### Segment by Trust Level
-
- Separate traffic into functional zones based on trust, not device count:
-
- - **Management** (VLAN 10): Hypervisors, switches, routers, IPMI/iLO -- highest trust
- - **Trusted/Lab** (VLAN 20): VMs, containers, workstation -- high trust
- - **IoT** (VLAN 30): Smart devices, cameras, sensors -- low trust, restricted
- - **Guest** (VLAN 40): Visitor devices -- zero trust, internet only
- - **Storage** (VLAN 50): NAS, iSCSI, backup targets -- high trust, limited access
- - **DMZ** (VLAN 99): Publicly exposed services -- medium trust, no inward access
-
- Start with 3-4 VLANs. Add more only with a clear security or performance reason. Over-segmentation adds complexity
- without proportional benefit. Separate production self-hosted services from experimental lab services -- prevent
- experimentation from causing downtime for household-facing apps.
-
- ### Inter-VLAN Policy
-
- VLANs without firewall rules provide zero security benefit. Every VLAN boundary needs explicit allow/deny policy.
- Default deny between all VLANs, then explicitly allow required flows. Always permit established/related return traffic.
-
- ### Infrastructure Requirements
-
- - Managed (VLAN-aware) switches with 802.1Q support
- - Router/firewall capable of VLAN termination and inter-VLAN routing
- - Access points with per-SSID VLAN tagging
- - Set native VLAN on trunks to an unused VLAN (not VLAN 1)
-
- ## Firewalls
-
- ### nftables
-
- Modern Linux firewall replacing iptables. Use `inet` family for dual-stack rules.
-
- Core structure: **tables** contain **chains**, chains contain **rules**. Base chains attach to Netfilter hooks (`input`,
- `forward`, `output`, `prerouting`, `postrouting`).
-
- Minimal host firewall:
-
- ```nft
- table inet filter {
- chain input {
- type filter hook input priority filter; policy drop;
- ct state established,related accept
- ct state invalid drop
- iifname "lo" accept
- icmp type echo-request accept
- icmpv6 type { echo-request, nd-neighbor-solicit, nd-router-advert } accept
- tcp dport { ssh } accept
- }
- chain forward {
- type filter hook forward priority filter; policy drop;
- }
- chain output {
- type filter hook output priority filter; policy accept;
- }
- }
- ```
-
- Key rules:
-
- - Place `ct state established,related accept` early in input/forward chains -- handles bulk of traffic efficiently
- - Drop `ct state invalid` packets explicitly
- - Use `policy drop` on input and forward chains (default deny)
- - Use `policy accept` on output chains (restrict outbound only when needed)
- - `accept` is not final across chains -- later chains at the same hook still evaluate. `drop` is always final.
- - Use `counter` on rules during development to verify traffic is hitting them
- - Persist rules: `nft list ruleset > /etc/nftables.conf`, enable `nftables.service`
-
- ### OPNsense / pfSense
-
- GUI-managed firewalls. Rules evaluate top-to-bottom, first match wins. Place more specific rules (e.g., block-LAN) above
- general rules (e.g., allow-internet) -- rule ordering mistakes are the most common cause of VLAN isolation failures.
-
- Post-install hardening (first 30 minutes):
-
- 1. Change default admin password
- 2. Enable 2FA (OPNsense: built-in; pfSense: package)
- 3. Disable web UI access from WAN
- 4. Configure DNS over TLS upstream
- 5. Enable automatic config backups
- 6. Restrict RFC1918 on WAN interface
- 7. Restrict DNS resolver to internal interfaces only (default allows queries from all interfaces -- open resolvers get
- abuse complaints)
-
- OPNsense has faster security patches and built-in 2FA. pfSense has a larger community knowledge base. Security posture
- depends more on configuration than platform choice.
-
- Throughput problems after install: disable hardware offloading (CRC, TSO, LRO) first -- this is the most common culprit
- in virtualized environments. If still slow, check IDS rulesets -- too many active rules kill performance. Start with 2-3
- rulesets, add more only as needed.
-
- ## IPv6
-
- ### Dual-Stack Configuration
-
- Run IPv4 and IPv6 concurrently. Dual-stack doubles the attack surface -- maintain identical security policies for both
- protocols. Use `inet` family in nftables for rules that apply to both stacks; use `ip6` only for IPv6-specific rules
- (ICMPv6, neighbor discovery).
-
- ### ICMPv6 Firewall Policy
-
- ICMPv6 is essential for IPv6 operation -- blocking all ICMPv6 breaks the network. Apply granular filtering:
-
- - **Must allow transit**: Destination Unreachable (Type 1), Packet Too Big (Type 2), Time Exceeded (Type 3) -- required
- for PMTU discovery and communication
- - **Link-local only**: Router/Neighbor Solicitation and Advertisement (Types 133-136) -- critical for local discovery,
- must never cross network boundaries
- - **Drop invalid**: Drop ICMPv6 from unexpected sources or with malformed headers
-
- ### Address Assignment
-
- - **SLAAC**: Stateless, no server needed. Devices auto-configure from router advertisements. Simple but less control.
- - **DHCPv6**: Stateful, centralized address management. Provides DNS server addresses. Use for servers requiring fixed
- addresses.
- - **Privacy extensions**: Randomize interface identifiers to prevent tracking. Enable for external communications,
- disable internally (rotating addresses break logging and service correlation).
-
- ### DNS and IPv6
-
- Add AAAA records only after IPv6 connectivity is verified and working. Premature AAAA records cause timeouts when IPv6
- is not properly configured. In dual-stack environments, test both A and AAAA resolution paths.
-
- ## IDS/IPS
-
- ### Suricata
-
- Network threat detection engine. Performs deep packet inspection and generates alerts based on rulesets. OPNsense
- includes Suricata built-in; pfSense requires a package.
-
- Performance impact: enabling 3 rulesets causes ~27% throughput drop. Start with 2-3 essential rulesets, add more only as
- needed. Disable hardware offloading (CRC, TSO, LRO) first if throughput is poor -- offloading conflicts with packet
- inspection.
+ Three biases decide most calls:
- ### CrowdSec
+ - **Evaluation order belongs to the engine, not the file.** nftables runs every base chain on a hook by priority and
+ only `drop` is final; Caddy sorts directives by a built-in table; nginx discards inherited `proxy_set_header` the
+ moment one is set lower; Traefik ranks routers by rule length; Authelia and cloudflared take the first match. Read the
+ engine's order before trusting the file's.
+ - **Who is trusted is a list, and every tool ships a different one.** Caddy trusts no proxy for `X-Forwarded-*`, HAProxy
+ and Cloudflare append the client's, Unbound refuses every client but localhost, AdGuard Home on `0.0.0.0` answers the
+ internet, Avahi reflects to every interface, Tailscale on Linux accepts no routes, NetBird pushes them unapproved.
+ Name the list in the config; never assume its direction.
+ - **Every filter has a second path around it.** IPv6 beside an `ip` rule in an `inet` table; a published container port
+ beside the host `input` chain; a flowtable beside the later hooks; a reachable origin beside Cloudflare Access; an
+ established connection beside a CrowdSec ban; a default-bridge container beside the LAN resolver. Close the second
+ path or state that it is open.
- Collaborative security engine that replaces or augments fail2ban. Key differences from fail2ban:
+ ## Filtering
- **fail2ban:** Detection via regex on local logs; local-only intelligence; iptables/nftables ban remediation; configured
- via jail.conf.
+ - **`accept` ends only the current base chain; `drop` ends the ruleset.** A permissive table added beside the one
+ Proxmox, Docker, or firewalld installs loads clean and changes nothing. A `nat` chain at priority -200 or below runs
+ before conntrack and translates nothing.
+ - **`ip` payload expressions match IPv4 only, also inside `inet`.** `ip protocol tcp` passes every IPv6 packet;
+ `meta l4proto tcp` matches both and skips IPv6 extension headers.
+ - **`flush ruleset` deletes every table on the host**, Docker's, firewalld's, and netavark's included, and no daemon
+ notices until restart. `flush table <family> <name>` is the scoped form. `nft -c -f` parses; it knows nothing about
+ interface names, runtime-populated sets, or a higher-priority table that drops first.
+ - **A published Docker port never traverses the host `input` chain**; it is DNAT and passes `forward`. Operator rules go
+ in `DOCKER-USER` on the iptables backend. Under the nftables backend (Engine 29.0, experimental) there is no
+ `DOCKER-USER` chain and no jump to it; a daemon switched between backends keeps the stale jump. Engine 28.0 blocks
+ direct-routed access to unpublished ports through the `raw` table.
+ - **On OPNsense and pfSense the order is floating, then group, then interface, and `quick` first-match is the default.**
+ A rule change does not touch the state table, so a new deny does nothing to a live session until states are reset, and
+ the default deny logs nothing unless told to.
- **CrowdSec:** Detection via YAML scenarios with leakspeed/capacity; community-shared threat data; Bouncers for
- remediation (firewall, Nginx, Traefik); configured via acquis.yaml + Hub collections.
+ Read [`${CLAUDE_SKILL_DIR}/references/filtering.md`] when writing or reading an nftables ruleset, a host that also runs
+ Docker, Podman, firewalld, or ufw, or an OPNsense or pfSense rule; it carries the evaluation, loading, coexistence, and
+ appliance rules and the kernel versions each feature needs.
- Install CrowdSec collections from the Hub for specific services (SSH, Nginx, Suricata). Configure whitelists immediately
- to prevent banning your own IPs.
+ ## Addressing and routing
- ### Suricata + CrowdSec Integration
+ - **A Linux bridge with `vlan_filtering 0` forwards tagged frames on every port.** That is the VLAN leak on a hypervisor
+ bridge. With filtering on, a port with no PVID drops every untagged frame, and turning filtering on before adding the
+ VLANs drops the management session.
+ - **The effective `rp_filter` is the maximum of `conf/all` and the interface**, and systemd's shipped sysctl file sets
+ every present interface to 2 while excluding `conf/all`, so a write to `all` succeeds and changes nothing. An
+ rp_filter drop shows in `tcpdump` and never in conntrack.
+ - **`accept_ra` at its default stops processing router advertisements when forwarding is turned on**; a router that gets
+ its WAN address from RA needs value 2. A dual-stack host prefers IPv4 over a ULA destination (RFC 6724), so a ULA-only
+ service never exercises its IPv6 rules.
+ - **A path-MTU black hole is a connection that establishes and stalls on volume.** The fix is passing ICMPv6 type 2 and
+ ICMP fragmentation-needed on the path, and MSS clamping in `forward` with `rt mtu` for both directions.
+ - **`KeepConfiguration=` defaults to `no`**, so restarting systemd-networkd drops DHCP addresses and routes with the SSH
+ session on them; `netplan try` reverts after 120 seconds and its own man page says to check whether it did.
- Suricata detects threats via packet inspection and logs to `fast.log`. CrowdSec parses these logs via `acquis.yaml` and
- makes blocking decisions. This separates detection (Suricata) from remediation (CrowdSec bouncers) -- each tool does
- what it does best.
+ Read [`${CLAUDE_SKILL_DIR}/references/routing.md`] when writing a VLAN, bridge, route, policy rule, sysctl, MTU, IPv6,
+ networkd, netplan, or DHCP setting; it carries the bridge, reverse-path, policy-routing, IPv6, and manager rules.
## DNS
- ### DNS Filtering
-
- Pi-hole and AdGuard Home are DNS sinkholes that block unwanted domains at the network level. Point DHCP-advertised DNS
- at the sinkhole.
-
- - **Pi-hole**: Lighter resource usage, larger community/blocklist ecosystem
- - **AdGuard Home**: Built-in encrypted DNS (DoH/DoT/DoQ), per-client rules, YAML config
-
- Deploy redundant pairs on separate hosts for resilience. Sync blocklists between instances. Avoid using
- firewall-integrated DNS blocking plugins (e.g., pfBlockerNG) -- a core firewall update can silently break the plugin and
- kill all DNS resolution. Run DNS filtering as a separate service.
-
- ### Split-Horizon DNS
-
- Return different DNS answers based on client network. Internal clients resolve to private IPs; external clients resolve
- to public IPs. Implementation:
-
- - Wildcard DNS rewrite in Pi-hole/AdGuard (`*.example.com -> 192.168.1.100`) for internal resolution to reverse proxy
- - Separate DNS instances for internal vs external (cleanest separation -- avoids AdGuard Home's global rewrite problem
- where a self-hosted DoH instance rewrites external queries with internal IPs)
- - Conditional forwarding for local domains to internal authoritative server
-
- ### mDNS Across VLANs
-
- mDNS (`.local`) is link-local and does not cross routed boundaries. Use Avahi with `enable-reflector=yes` to bridge mDNS
- between specific VLANs (e.g., trusted LAN discovering IoT devices). Restrict interfaces -- never reflect to guest or
- WAN.
-
- ### Upstream Encryption
-
- Always encrypt DNS queries upstream (DNS over TLS or HTTPS) to prevent ISP snooping. Configure the recursive resolver to
- use encrypted upstream; serve plain DNS internally.
-
- ## Reverse Proxy
-
- Single entry point for all HTTP/HTTPS services. Terminates TLS, routes by hostname, eliminates per-service port
- exposure.
-
- ### Tool Selection
-
- | Factor | Caddy | Traefik | Nginx Proxy Manager |
- | -------------- | ---------------- | -------------------- | ------------------- |
- | Config style | Caddyfile | YAML + Docker labels | Web UI |
- | Auto HTTPS | Default behavior | Via cert resolvers | One-click |
- | Docker-aware | Plugin | Native | No |
- | Learning curve | Low | Medium | Lowest |
- | Best for | Simple setups | Docker/K8s stacks | GUI preference |
-
- - **Docker-heavy**: Traefik -- discovers containers automatically
- - **Simplicity-first**: Caddy -- automatic HTTPS, minimal config
- - **GUI management**: Nginx Proxy Manager -- point-and-click
-
- ### Architecture Pattern
-
- ```
- Client --[HTTPS]--> Reverse Proxy --[HTTP]--> Backend Services
- ```
-
- Only the reverse proxy needs TLS certificates. Backend services run plain HTTP on internal networks. One wildcard
- certificate covers all services.
-
- ### Wildcard Certificates
-
- Use DNS-01 ACME challenge for wildcard certs (`*.example.com`). Required for internal services not reachable from the
- internet. Caddy and Traefik handle this natively with DNS provider plugins.
-
- ## Authentication Proxies
-
- ### Authelia vs Authentik
-
- Both provide SSO and MFA for self-hosted services. Choose based on resource constraints and protocol needs:
-
- - **Authelia**: Lightweight (~20 MB image, ~30 MB RAM). YAML-configured, no GUI. Supports OIDC, TOTP, WebAuthn,
- Passkeys. Best for homelabs with limited resources needing web SSO.
- - **Authentik**: Full identity provider (~690 MB, requires PostgreSQL). Web GUI with user dashboard. Supports OIDC,
- SAML2, LDAP, RADIUS, SCIM. Best when SAML support or a user portal is needed.
-
- ### Forward Auth Pattern
-
- Reverse proxies intercept requests and forward headers to the auth server before reaching the backend. Traefik uses
- `ForwardAuth` middleware; Caddy uses similar mechanisms. The auth server validates the session and returns 200 (allow)
- or 401 (redirect to login).
-
- ### Deployment
-
- Gate user-facing services behind auth proxies. Keep admin panels (router, Proxmox, NAS) on VPN or management VLAN only
- -- do not expose through public auth proxies. Prefer phishing-resistant MFA (FIDO2/WebAuthn) over TOTP where possible.
-
- ## Cloudflare Tunnels
-
- ### When to Use
-
- Cloudflare Tunnels expose internal services without opening inbound ports. Useful behind CGNAT or when port forwarding
- is impossible. The `cloudflared` daemon initiates outbound connections to Cloudflare's edge.
-
- ### Architecture Pattern
-
- ```
- Internet -> Cloudflare Edge -> cloudflared -> Reverse Proxy -> Backend
- ```
-
- Route tunnels into a local reverse proxy (Traefik/Caddy) for SSL termination and routing. Layer Authelia/Authentik or
- Cloudflare Access for identity gating.
-
- ### When NOT to Use
-
- - **Admin panels**: Router, hypervisor, NAS UIs -- keep on VPN/LAN only
- - **Sensitive data services**: Password managers, private document portals
- - **LAN-only tools**: Printer dashboards, IoT hubs with weak/no auth
- - **High-bandwidth media**: Jellyfin/Plex through CDN may violate ToS -- disable caching for media subdomains if used
-
- A tunnel makes services accessible, not secure. Weak credentials, missing MFA, or unpatched software remain exploitable
- regardless of the tunnel.
-
- ## VPN
-
- ### WireGuard
-
- Kernel-level VPN protocol. ~4,000 lines of code. Fast, simple, no cipher negotiation.
-
- - Hub-and-spoke topology for remote access VPN
- - Site-to-site between fixed endpoints with static IPs
- - Requires manual port forwarding (UDP 51820) and key management
- - ~5 MB RAM (kernel module)
- - Use when: zero external dependencies required, resource-constrained devices, traditional VPN gateway needed
-
- ### Tailscale
-
- Mesh VPN built on WireGuard. Automatic key management, NAT traversal, DNS.
-
- - Full mesh topology -- devices connect directly peer-to-peer
- - Zero-config: SSO login, no port forwarding, no config files
- - MagicDNS for automatic device name resolution
- - Subnet routers expose local networks without per-device installation
- - ~30-50 MB RAM (userspace daemon)
- - Use when: multiple devices across networks, NAT traversal needed, want managed ACLs and DNS
-
- ### Topology Resilience
-
- Hub-and-spoke has a single point of failure: if the hub goes down, the entire VPN is unreachable. Mesh (Tailscale) is
- resilient -- nodes remain connected to each other even if one fails. Choose topology based on failure tolerance, not
- just convenience.
-
- ### Hybrid Pattern
-
- Run both: Tailscale for day-to-day remote access (mesh, zero-config), WireGuard as backup VPN gateway for full-tunnel
- routing or when Tailscale's coordination server is unreachable.
-
- ### OpenVPN
-
- Legacy. Slower, larger attack surface. Choose only when TCP-based tunneling is required to bypass UDP-blocking
- firewalls. For all new deployments, use WireGuard or Tailscale.
-
- ## TLS/SSL Certificates
-
- ### ACME and Let's Encrypt
-
- Free, automated certificate issuance. Certificates valid 90 days, renew at 60.
-
- Challenge types:
+ - **Docker rewrites a container's `resolv.conf`.** Loopback nameservers inherited from the host are removed, and when
+ nothing is left Google's resolvers are substituted, so on a systemd-resolved host every default-bridge container
+ bypasses the LAN filter. A `--dns 127.0.0.53` override resolves inside the container's namespace and always fails.
+ - **The resolved stub owns `127.0.0.53:53` by default**, and only the stub-mode symlink routes clients through resolved;
+ uplink mode bypasses it. Another daemon binding the wildcard on port 53 fails until `DNSStubListener=no`.
+ - **Unbound refuses every client but localhost until `access-control` allows it, listens on localhost until `interface:`
+ names another, and changes interfaces only on restart.** `home.arpa` is a default static zone, so a zone or forwarder
+ under it is shadowed until declared with another type or `nodefault`; `do-not-query-localhost` defaults to `yes`, so a
+ forwarder on `127.0.0.1` gets SERVFAIL.
+ - **Pi-hole v6 is a different surface from v5.** Every container variable was renamed to `FTLCONF_<section>_<key>`,
+ nineteen were removed, an unknown one is ignored, an env-supplied setting is read-only in the UI, `/etc/dnsmasq.d` is
+ ignored unless `misc.etc_dnsmasq_d` is set, and the web server falls back to 8080 when 80 is taken. FTL 6.3 renamed
+ `dns.domain` to `dns.domain.name`.
+ - **AdGuard Home overwrites an edit made to `AdGuardHome.yaml` while running**, `allowed_clients` set makes
+ `disallowed_clients` ignored, and `bind_hosts: ['0.0.0.0']` with neither is an open resolver whose first signal is an
+ abuse notice.
+ - **Private naming**: `.local` is mDNS and some hosts never ask the LAN resolver for it; `home.arpa` is unsigned by
+ design; no public CA issues for `.lan`, `.home`, `.internal`, or any unregistered suffix, so internal TLS needs a
+ registered domain or a private CA. A wildcard covers one label and not the bare domain.
+ - **Avahi's reflector reflects to every interface it joined**, and it joins every interface except loopback and
+ point-to-point unless `allow-interfaces` scopes it.
- - **HTTP-01**: CA fetches token from port 80. For public-facing servers.
- - **DNS-01**: Client creates TXT record. Required for wildcards and internal services.
- - **TLS-ALPN-01**: CA connects to port 443. When only 443 is available.
+ Read [`${CLAUDE_SKILL_DIR}/references/dns.md`] when touching resolved, a container's DNS, search domains, private
+ naming, caching, or mDNS, and [`${CLAUDE_SKILL_DIR}/references/resolvers.md`] when writing Unbound, Pi-hole, AdGuard
+ Home, dnsmasq, CoreDNS, or Knot Resolver configuration; the second carries the per-daemon defaults, the local-zone
+ types, the renamed keys, and the upstream syntax.
- ### Recommended Approach
+ ## Proxies and TLS
- Let the reverse proxy handle all certificate management. Caddy and Traefik do this automatically. For standalone cert
- management, use acme.sh (shell-only, no dependencies, many DNS providers).
+ - **Header trust differs per proxy.** Caddy discards inbound `X-Forwarded-*` unless the sender is in `trusted_proxies`,
+ and even then parses `X-Forwarded-For` left to right unless `trusted_proxies_strict` is set; Traefik strips unless
+ `forwardedHeaders.trustedIPs` names the sender; nginx's realip module replaces nothing until `set_real_ip_from` is
+ set; HAProxy's `option forwardfor` and Cloudflare append the client's value. Read `CF-Connecting-IP` behind
+ Cloudflare, never the first `X-Forwarded-For`.
+ - **Caddy sorts directives by a built-in table**, `handle` blocks are mutually exclusive, a path matcher is exact, and a
+ site block never cascades. `auto_https off` does not make a domain site block serve HTTP; the address does.
+ `tls client_auth` with no `trust_pool` demands a certificate and verifies nothing. A `caddy reload` with an unchanged
+ file is a no-op, and `SIGUSR1` is ignored once the admin API has been used.
+ - **Traefik v3 rejects v2 rule syntax** unless `core.defaultRuleSyntax: v2`, itself deprecated. `certificatesResolvers`
+ in a dynamic file is ignored and the default self-signed certificate is served; a router with no `entryPoints`
+ attaches to all of them; priority is rule length; `exposedByDefault` is `true`; a header whose name aliases to the
+ same backend variable passes through by default. Renewal is computed from `acme.certificatesDuration`, never from the
+ certificate.
+ - **nginx `proxy_set_header` at any level discards every inherited one**, `proxy_pass` with a URI replaces the matched
+ prefix and without one passes the request URI unchanged, and a literal upstream hostname is resolved once at start
+ unless `resolve` (open source from 1.27.3) or a variable plus `resolver` is used.
+ - **ACME as written**: HTTP-01 is port 80 only, validation prefers AAAA and retries IPv4 only on a timeout, a wildcard
+ is one label and not the apex, DNS-01 propagation waits default near zero, and rate limits are token buckets a retry
+ loop drains. ARI overrides the client's renewal window. The lifetime schedule, the profile facts, and the rate-limit
+ numbers are in the devops skill's `references/secrets.md`.
- ### TLS Configuration
+ Read [`${CLAUDE_SKILL_DIR}/references/caddy.md`], [`${CLAUDE_SKILL_DIR}/references/traefik.md`], or
+ [`${CLAUDE_SKILL_DIR}/references/nginx.md`] when writing that proxy's configuration; read
+ [`${CLAUDE_SKILL_DIR}/references/acme.md`] when a challenge, wildcard, propagation, profile, or client storage setting
+ is in play; read [`${CLAUDE_SKILL_DIR}/references/cloudflare.md`] when Cloudflare or a `cloudflared` tunnel is in the
+ path, because `Full` mode validates nothing on the origin and a dashboard-managed tunnel ignores the local ingress file.
- - Minimum TLS 1.2, prefer TLS 1.3
- - Enable HSTS: `Strict-Transport-Security: max-age=31536000; includeSubDomains`
- - Enable OCSP stapling when supported
- - Don't manually configure cipher suites unless you have a specific reason -- modern defaults are optimal
- - Monitor certificate expiry (Prometheus blackbox_exporter, Uptime Kuma)
+ ## Identity at the proxy
- ## Security Hardening
+ - **The backend must receive identity headers from the proxy only, and the proxy must strip inbound ones explicitly.**
+ Caddy's `forward_auth copy_headers` copies a header only when the auth response carries it, and Traefik replaces the
+ canonical dash form only, so `X_Auth_User` from a client reaches a CGI or PHP backend as the same variable. Trusting a
+ whole Docker network is not enough; Authelia's page says any container on it can then authenticate to any application.
+ - **Authelia's endpoint depends on the proxy**: `/api/authz/forward-auth` for Traefik, Caddy, and HAProxy,
+ `/api/authz/auth-request` for nginx. `/api/verify` answers as the `Legacy` implementation in 4.38 and later, so a
+ stale config keeps working while options added in 4.38 and later cannot be used with it. Access control is first-match
+ with `default_policy` `deny`; `resources` are unanchored regular expressions; a host not covered by `session.cookies`
+ is denied automatically.
+ - **authentik trusts forwarded headers only from `AUTHENTIK_LISTEN__TRUSTED_PROXY_CIDRS`** (2026.8 and later), private
+ ranges by default, replaced rather than extended by a set value. Only `/outpost.goauthentik.io/*` is routed to the
+ outpost, and each proxy needs a second route for it or the login callback 404s.
- ### SSH
+ Read [`${CLAUDE_SKILL_DIR}/references/forward-auth.md`] when wiring Authelia, authentik, or oauth2-proxy to a proxy; it
+ carries the per-proxy header lists, the rule-ordering traps, the secrets rules, and the injection advisories.
- - Disable password authentication -- key-based only (Ed25519)
- - Disable root login -- authenticate as user, then `sudo`
- - Restrict agent/X11 forwarding
- - Use CrowdSec or fail2ban as a mitigation layer (not a substitute for key-based auth)
- - For fleet management: use SSH Certificate Authority (step-ca) instead of distributing authorized_keys. SSH CA issues
- short-lived certificates (~16h), eliminating stale key accumulation. Configure `TrustUserCAKeys` in sshd_config to
- point at the CA public key.
+ ## Tunnels
- ### Network Hardening Checklist
+ - **`AllowedIPs` is the outbound route and the inbound source ACL in one field**, and two peers on one interface
+ claiming the same prefix resolve silently in favor of the later one. A fresh `latest handshake` proves key agreement
+ and nothing about routes.
+ - **A `/0` in any peer's `AllowedIPs` with `Table` unset switches wg-quick to policy routing** and supersedes the host's
+ default route the moment `up` returns; the `/1` split does not get that treatment and blackholes instead.
+ `Table = off` is the setting for a routing daemon over the tunnel.
+ - **wg-quick does not default MTU to 1420**; it computes the smallest endpoint route MTU minus 80, took the largest
+ before wireguard-tools v1.0.20250521, and Debian 13 and Ubuntu 24.04 ship the 2021 release. `SaveConfig = true`
+ rewrites the file on every `down`, keeping hooks and dropping comments. `DNS =` calls `resolvconf` and nothing else.
+ - **Tailscale on Linux accepts no advertised routes until `--accept-routes`**, an advertised route reaches nobody until
+ approved, `tailscale up` with any flag demands the complete set and `--reset` silently defaults the rest,
+ `autogroup:internet` is the only thing that authorizes exit-node use, and tagging a device disables its key expiry.
+ Headscale rejects a client below its capability floor, blocks minor-version skips from 0.29, and redefined `*` to the
+ tailnet ranges in the same release.
+ - **A `cloudflared` tunnel managed from the dashboard ignores the local ingress file**, the last local rule must be a
+ catch-all, and a tunnel leaves a host with a public address as reachable as before.
- - Default deny inbound on all firewall interfaces
- - Filter outbound traffic -- a compromised service with unrestricted outbound can exfiltrate data or contact C2 servers.
- At minimum: allow DNS only to designated DNS servers, allow HTTP/HTTPS for updates, block all other outbound from IoT
- and untrusted VLANs
- - Restrict management interfaces to management VLAN only
- - Enable DHCP snooping on managed switches -- prevents rogue DHCP servers
- - Enable Dynamic ARP Inspection (DAI) -- prevents ARP poisoning (requires DHCP snooping binding table)
- - Enable port security -- limits MAC addresses per port, prevents CAM table overflow attacks
- - Disable DTP on all user-facing ports -- prevents VLAN hopping
- - Enable BPDU Guard on access ports -- prevents STP manipulation
- - Prefer DHCP reservations over static IPs for centralized management
- - Change all default credentials (every device, no exceptions)
- - Update firmware on all network devices regularly
- - Backup configurations before any change, store off-device
- - Disable unused services (UPnP, WPS, SNMP v1/v2 on WAN)
- - For IPv6: maintain identical firewall policies for both stacks, allow required ICMPv6 types, disable IPv6 if not
- actively used
- - Document network topology and keep diagrams current
+ Read [`${CLAUDE_SKILL_DIR}/references/wireguard.md`] when writing a WireGuard or wg-quick file or routing a daemon over
+ it, and [`${CLAUDE_SKILL_DIR}/references/tailscale.md`] when writing a Tailscale policy or `up` invocation, a Headscale
+ configuration, or a NetBird route; cloudflared is in the cloudflare reference above.
- ### Monitoring
+ ## Detection and remediation
- Deploy Prometheus + Grafana for network observability:
+ - **Suricata's shipped `HOME_NET` is the three RFC 1918 ranges**, so a sensor on a public prefix loads every rule and
+ alerts on nothing. Rules ship as `alert`; IPS drops nothing without `drop.conf`. `suricata-update` does not reload the
+ engine unless `--reload-command` is set. Offloads that merge frames (GRO, LRO, TSO) must be off on the capture
+ interface, AF_PACKET IPS needs a symmetric interface pair and drops nothing on a box that routes instead of bridges,
+ and an NFQUEUE with no listener drops the link when Suricata stops.
+ - **CrowdSec's firewall component creates its rules once at startup**, so a ruleset flush or a firewalld reload removes
+ the enforcement while decision syncs keep logging success; `nftables_hooks` omitted means `input` only; a banned IP
+ with an established connection stays connected; behind Cloudflare it blocks nothing because it sees only Cloudflare's
+ addresses. The ban duration is in `profiles.yaml`, not the scenario. `cscli dashboard` is gone since 1.7.0 and a
+ container without a data volume exits 0.
+ - **fail2ban's `banaction` defaulted to `iptables-multiport` through 1.1.0** and fails silently on an nftables-only
+ host; 1.1.1 moved the default to `nftables`. The sshd filter matched nothing after OpenSSH 9.8's `sshd-session` rename
+ until 1.1.1; `ignoreip` is commented out, and `ignoreself` never covers the operator's remote address.
- - **snmp_exporter**: Switch/router metrics via SNMP
- - **blackbox_exporter**: Endpoint probing, certificate monitoring
- - **node_exporter**: Host-level metrics
- - Centralize logs with syslog or Loki+Promtail
- - Use Monit or systemd watchdog to auto-restart critical services (DNS resolvers, reverse proxies) -- silent failures
- cause household-wide outages
- - Review firewall deny logs weekly during initial setup
+ Read [`${CLAUDE_SKILL_DIR}/references/suricata.md`] when writing a Suricata config or ruleset or enabling IPS, and
+ [`${CLAUDE_SKILL_DIR}/references/remediation.md`] when configuring CrowdSec, a remediation component, or fail2ban.
## Application
- When **designing** network architecture: propose segmented designs with explicit trust boundaries. Default to
- security-first configurations. Present trade-offs between complexity and security.
-
- When **implementing** configurations: produce production-ready config files, not toy examples. Include comments
- explaining non-obvious choices. Test connectivity after changes.
-
- When **debugging** network issues: start at layer 2 (link/VLAN), work up through layer 3 (routing/firewall), then layer
- 7 (application/proxy). Check firewall deny logs first -- they reveal 80% of connectivity issues. Key tools:
-
- - `ping` / `traceroute` -- reachability and path verification
- - `dig` / `nslookup` -- DNS resolution from specific resolvers
- - `ss -tlnp` / `netstat` -- listening ports and socket state
- - `tcpdump` / `tshark` -- packet captures for traffic flow analysis
- - `nft list ruleset` -- verify active firewall rules match intent
- - `avahi-browse -a` -- verify mDNS discovery across VLANs
- - `curl -v` -- HTTP/HTTPS connectivity with TLS details
+ When **writing** network configuration, apply these rules silently; do not narrate a rule while following it. Where the
+ target's existing configuration contradicts one, follow it for the change at hand and flag the divergence once. Name the
+ tool version an edit assumes when the syntax differs across a boundary named here: Pi-hole v6, Traefik v3, Docker Engine
+ 28 and 29, wireguard-tools v1.0.20250521, Headscale 0.28 and 0.29, CrowdSec 1.7, fail2ban 1.1.1, Unbound 1.23.
- When **reviewing** network configurations: flag missing inter-VLAN firewall rules, default credentials, disabled
- encryption, overly permissive access, and missing monitoring. State what's wrong and how to fix it.
+ When **reviewing** network configuration, cite the line, state what it does once loaded, and give the corrected line
+ inline. Do not lecture.
## Integration
- The **containers** skill covers Docker/Podman networking (bridge, host, macvlan). This skill covers the network
- infrastructure those containers sit on. When a task involves both container networking and VLAN/firewall design, both
- skills apply.
-
- The **ansible** skill handles automation of network configuration deployment. Use this skill for what to configure,
- ansible for how to deploy it.
-
- **Security is not optional. Every default must be secure. Convenience follows.**
+ The **devops** skill governs the change: what it must carry before apply, how it is applied, and the evidence that
+ closes it, including the read-only effect checks for a firewall, a resolver, a certificate, and a WireGuard peer in its
+ `references/effect.md`. This skill governs what a ruleset, zone, tunnel, or proxy configuration does once loaded, and
+ wins on that question. The **containers** skill owns container networking modes and Compose; the host-firewall side of a
+ published port is here. The **proxmox** skill owns the hypervisor bridge setting; Linux bridge semantics are here. The
+ **coding** skill governs the shape of the work. All are active at once.