VPS Hardening — OVH Edge Network Firewall
Server: vps-9d58eb96.vps.ovh.ca — 149.56.132.214 (OVH VPS, Beauharnois)
Stack: Dokploy (Docker Swarm, single node) + Traefik v3 + n8n + vgm-playwright + Redis/Postgres
Dokploy request logs showed constant scanner noise against the raw IP: GET /.env,
path traversal (/..%2F..%2F...%2Fetc%2Fpasswd), probe POSTs — 1600+ entries, all 404.
The noise itself was harmless (nothing served), but the audit it triggered found real exposure.
Audit findings (ss -tlnp + docker ps)
Section titled “Audit findings (ss -tlnp + docker ps)”| Finding | Severity | Status |
|---|---|---|
Dokploy admin panel published on 0.0.0.0:3000 — deploy-anything UI on the public internet |
High | Blocked by edge firewall; port un-publish still pending |
Docker Swarm manager port 2377 listening on * |
Medium (mTLS-protected, but no reason to expose) | Blocked |
Swarm gossip 7946 + overlay VXLAN 4789/udp public |
Medium (gossip unauthenticated) | Blocked |
LLMNR 5355 on 0.0.0.0 |
Low | Blocked |
| App containers (playwright, n8n, redis, postgres, redisinsight, stirling-pdf) | OK — no published ports, only reachable via Traefik / internal docker networks | — |
| exim4 | OK — loopback only | — |
Note: a host firewall (ufw/iptables INPUT) could not have fixed the Docker-published ports — Docker’s DNAT bypasses the INPUT chain. The OVH edge firewall filters before traffic reaches the VM, which is why it was chosen over fail2ban/ufw.
Edge firewall rules (OVH Manager → Network → IP → 149.56.132.214)
Section titled “Edge firewall rules (OVH Manager → Network → IP → 149.56.132.214)”The OVH edge firewall is stateless: “established” is a TCP-flag check, and UDP has no state at all — hence the explicit rules for DNS/NTP responses to outbound queries.
| Priority | Mode | Proto | Source | Src port | Dest port | TCP status | Purpose |
|---|---|---|---|---|---|---|---|
| 0 | Authorise | TCP | 184.149.58.157/32 | — | 22 | — | SSH, allowed IP #1 |
| 1 | Authorise | TCP | 208.111.82.254/32 | — | 22 | — | SSH, allowed IP #2 |
| 2 | Authorise | TCP | all | — | 80 | — | Traefik HTTP (Let’s Encrypt + redirect) |
| 3 | Authorise | UDP | all | 123 | — | — | NTP responses (clock sync) |
| 4 | Authorise | UDP | all | — | 443 | — | HTTP/3 |
| 5 | Authorise | TCP | all | — | — | established | Return traffic for all outbound TCP (portal calls, proxy, APIs, apt, docker pulls) |
| 6 | Authorise | TCP | all | — | 443 | — | Traefik HTTPS — webhooks (Trello, Typeform), n8n, all apps |
| 7 | Authorise | UDP | all | 53 | — | — | DNS responses — mandatory, deny-all would break resolution |
| 8 | Authorise | UDP | all | — | 41641 | — | Tailscale direct path (falls back to DERP relay without it) |
| 9 | Authorise | ICMP | all | — | — | — | OVH monitoring, path MTU |
| 19 | Refuse | IPv4 | all | — | — | — | Deny everything else — the rule that makes the rest matter |
As configured in OVH Manager (all rules Active, firewall Enabled):

Gotchas hit during setup, for next time:
- Source port ≠ destination port. First SSH rule was drafted with source port 22 — clients connect from ephemeral ports; the rule would have matched nothing and caused lockout when deny-all activated. Source port stays empty on inbound-service rules.
- A destination-port-less TCP Authorise = allow all TCP. One rule was briefly created without a port and would have neutralised the firewall. Deleted before activation.
- One source IP per rule — two allowed SSH IPs = two rules.
- Rules do nothing until the firewall toggle is flipped to Enabled; propagation took ~1 minute after enabling.
Verification (from outside the VM)
Section titled “Verification (from outside the VM)”- Port 3000 (Dokploy UI): dropped ✓
- Port 2377 / 7946 (swarm): dropped ✓
- Port 443: open,
n8n.vgmautomations.com→ HTTP 200 ✓ — inbound webhooks unaffected - Port 80: open ✓
- SSH: verified working from both allowlisted IPs ✓
Rescue path
Section titled “Rescue path”Both allowlisted SSH IPs are dynamic. If they rotate and SSH is refused:
Tailscale is on the VM and unaffected by the edge firewall — ssh debian@100.79.15.82 —
then update rule 0/1 sources in OVH Manager.
Still open
Section titled “Still open”- Un-publish port 3000 (defense in depth — currently blocked only by the firewall):
assign a panel domain in Dokploy Settings → Server, confirm it loads, then
docker service update --publish-rm published=3000,target=3000 dokploy, and enable 2FA on the Dokploy account. - IPv6 is unfiltered — the OVH edge firewall on this VPS is IPv4-only and the VM has
a public v6 (
2607:5300:205:200::9672). Acceptable while no AAAA records point at it; verify withdig AAAA <domain>for each zone, or disable v6 on the host if unused. - Optional: CrowdSec (SSH + Traefik logs) — polish, no longer urgent.
Non-goals
Section titled “Non-goals”- fail2ban was considered and rejected as the primary fix: log-based banning reacts to noise that the firewall now prevents from mattering. Still reasonable later for sshd.
- Scanner 404s against 80/443 will keep appearing in Dokploy/Traefik logs. That is expected background noise hitting a closed door — not an incident.