#!/usr/sbin/nft -f # vanguard-stream host firewall. Own table so (re)applying never disturbs Docker's rules. # Input only. MediaMTX runs on the host network, so 1935, 8890/udp and 8189 arrive through THIS chain (the # accepts below). Docker-published ports (22000) arrive via Docker's forward chain and are # listed here for the host-network case (Caddy) and clarity. Nothing else should listen publicly. destroy table inet vanguard table inet vanguard { set ssh_allow { type ipv4_addr flags interval # leave empty to allow SSH from anywhere (key auth, rate limited); or list CIDRs: # elements = { 203.0.113.0/24, 198.51.100.7 } } set db_allow { type ipv4_addr flags interval # MariaDB clients. Leave empty = anywhere (rate limited); or list the bot dev's IPs and then # delete the "tcp dport 3306 ct state new accept" line below to restrict to this set. # elements = { 203.0.113.7 } } chain input { type filter hook input priority 0; policy drop; iif lo accept ct state established,related accept ct state invalid drop # icmp (ping, pmtu) and all icmpv6 (neighbor discovery is mandatory) ip protocol icmp icmp type { echo-request, destination-unreachable, time-exceeded, parameter-problem } accept ip6 nexthdr icmpv6 accept # dhcp client replies (OVH may hand out the address by DHCP) udp sport 67 udp dport 68 accept # ssh: restricted to ssh_allow if it has entries, otherwise anywhere, rate limited tcp dport 22 ct state new ip saddr @ssh_allow accept tcp dport 22 ct state new limit rate over 15/minute burst 10 packets drop tcp dport 22 ct state new accept # public services tcp dport { 80, 443 } accept # Caddy: copyparty, dock, dump udp dport 443 accept # Caddy HTTP/3 (QUIC) tcp dport 1935 accept # RTMP ingest udp dport 8890 accept # SRT ingest tcp dport 22000 accept # Syncthing tcp dport 2222 accept # Gitea ssh (git clone/push) udp dport 8189 accept # WebRTC media for the live portal tcp dport 8189 accept # WebRTC ICE-TCP fallback udp dport 22000 accept # Syncthing QUIC # mariadb (host network): allowlist first, then anywhere with a brute-force limit tcp dport 3306 ct state new ip saddr @db_allow accept tcp dport 3306 ct state new limit rate over 30/minute burst 20 packets drop tcp dport 3306 ct state new accept # dumpd /mtx-auth for MediaMTX (container -> host gateway): only from Docker bridge interfaces iifname { "docker0", "br-*" } tcp dport 8081 accept # NOT opened on purpose — these bind to 127.0.0.1 only and are reached via Caddy or an ssh tunnel: # 9000 authentik · 8096 jellyfin · 3923 copyparty · 8384 syncthing UI · 8080 dumpd # Docker publishes them on loopback, so its NAT never forwards outside traffic to them either. # everything else: drop (policy), but log a trickle for diagnosis limit rate 5/minute log prefix "vg-drop " level info } }