{"id":5882,"date":"2026-02-20T11:43:43","date_gmt":"2026-02-20T06:13:43","guid":{"rendered":"https:\/\/toolswift.com\/blog\/?p=5882"},"modified":"2026-02-20T11:43:43","modified_gmt":"2026-02-20T06:13:43","slug":"self-hosting-a-global-edge-waf-distributing-traefik-across-edge-vps-nodes-with-centralized-crowdsec-banishment-for-ddos-mitigation","status":"publish","type":"post","link":"https:\/\/toolswift.com\/blog\/self-hosting-a-global-edge-waf-distributing-traefik-across-edge-vps-nodes-with-centralized-crowdsec-banishment-for-ddos-mitigation\/","title":{"rendered":"Self-Hosting a Global Edge WAF: Distributing Traefik Across Edge VPS Nodes with Centralized CrowdSec Banishment for DDoS Mitigation"},"content":{"rendered":"<p>Public-facing services in 2026 operate in a hostile environment. Automated scanners, credential stuffing bots, layer 7 DDoS floods, and exploit kits continuously target exposed endpoints. At the same time, latency expectations are stricter than ever, and multi-region deployments are the norm rather than the exception.<\/p>\n<p>Many organizations rely on managed edge providers for Web Application Firewall (WAF) and DDoS mitigation. However, there are valid scenarios where self-hosting a global edge layer makes sense:<\/p>\n<ul>\n<li>Cost predictability at scale<\/li>\n<li>Full control over routing and inspection logic<\/li>\n<li>Compliance or data sovereignty constraints<\/li>\n<li>Homelab and hybrid-cloud deployments<\/li>\n<li>Specialized routing needs not supported by commercial CDNs<\/li>\n<\/ul>\n<p>This article outlines a production-grade architecture for self-hosting a global edge WAF using:<\/p>\n<ul>\n<li>Traefik as the edge reverse proxy and TLS terminator<\/li>\n<li>CrowdSec for behavioral detection and IP reputation intelligence<\/li>\n<li>Multiple globally distributed VPS nodes<\/li>\n<li>Centralized ban propagation for coordinated DDoS mitigation<\/li>\n<\/ul>\n<p>The result is a distributed edge layer capable of absorbing volumetric noise, blocking malicious actors in near real time, and routing legitimate traffic to origin services.<\/p>\n<h2>Architecture Overview<\/h2>\n<h3>High-Level Design<\/h3>\n<p>The architecture consists of the following components:<\/p>\n<ul>\n<li><strong>Edge VPS Nodes (Global)<\/strong>\n<ul>\n<li>Traefik (reverse proxy + TLS)<\/li>\n<li>CrowdSec agent<\/li>\n<li>CrowdSec bouncer (Traefik middleware)<\/li>\n<li>Node-level firewall (iptables\/nftables)<\/li>\n<\/ul>\n<\/li>\n<li><strong>Central CrowdSec Control Plane<\/strong>\n<ul>\n<li>CrowdSec LAPI (Local API server)<\/li>\n<li>PostgreSQL backend<\/li>\n<li>Shared ban database<\/li>\n<\/ul>\n<\/li>\n<li><strong>Origin Infrastructure<\/strong>\n<ul>\n<li>Application servers<\/li>\n<li>Private network only (not publicly exposed)<\/li>\n<\/ul>\n<\/li>\n<li><strong>Global DNS<\/strong>\n<ul>\n<li>Geo-aware or latency-based routing<\/li>\n<li>Health-checked failover<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<h3>Traffic Flow<\/h3>\n<ol>\n<li>Client resolves DNS \u2192 nearest edge VPS.<\/li>\n<li>Traffic hits Traefik.<\/li>\n<li>Traefik middleware queries CrowdSec bouncer.<\/li>\n<li>If IP is banned \u2192 403 or drop.<\/li>\n<li>If allowed \u2192 forwarded to origin over private network or secure tunnel.<\/li>\n<li>Logs are parsed by CrowdSec agent.<\/li>\n<li>Detected malicious behavior \u2192 ban decision pushed to central LAPI.<\/li>\n<li>All edge nodes pull updated ban list.<\/li>\n<\/ol>\n<h3>Component Integration<\/h3>\n<ul>\n<li>Traefik acts as both reverse proxy and WAF entrypoint.<\/li>\n<li>CrowdSec analyzes access logs and applies detection scenarios.<\/li>\n<li>CrowdSec LAPI acts as a centralized decision authority.<\/li>\n<li>Traefik bouncers enforce decisions at request time.<\/li>\n<\/ul>\n<p>This separation ensures detection is distributed but enforcement is globally synchronized.<\/p>\n<h2>Implementation Breakdown<\/h2>\n<h3>Step 1: Provision Global Edge VPS Nodes<\/h3>\n<p>Minimum recommended per node:<\/p>\n<ul>\n<li>2 vCPU<\/li>\n<li>2\u20134 GB RAM<\/li>\n<li>10 Gbps virtual NIC preferred<\/li>\n<li>Ubuntu 24.04 LTS or Debian 12<\/li>\n<\/ul>\n<p>Open only:<\/p>\n<ul>\n<li>TCP 80<\/li>\n<li>TCP 443<\/li>\n<li>SSH (restricted via firewall)<\/li>\n<\/ul>\n<p>Disable password SSH and enforce key-only access.<\/p>\n<h3>Step 2: Deploy Central CrowdSec LAPI<\/h3>\n<p>Use a dedicated small VPS or internal server.<\/p>\n<h3>Docker Compose for Central LAPI<\/h3>\n<pre><code>version: \"3.9\"\r\n\r\nservices:\r\n  crowdsec:\r\n    image: crowdsecurity\/crowdsec:latest\r\n    container_name: crowdsec-lapi\r\n    restart: unless-stopped\r\n    environment:\r\n      - COLLECTIONS=crowdsecurity\/nginx\r\n    volumes:\r\n      - .\/config:\/etc\/crowdsec\r\n      - .\/data:\/var\/lib\/crowdsec\/data\r\n    ports:\r\n      - \"8080:8080\"\r\n    depends_on:\r\n      - db\r\n\r\n  db:\r\n    image: postgres:15\r\n    container_name: crowdsec-db\r\n    restart: unless-stopped\r\n    environment:\r\n      POSTGRES_USER: crowdsec\r\n      POSTGRES_PASSWORD: strongpassword\r\n      POSTGRES_DB: crowdsec\r\n    volumes:\r\n      - .\/pgdata:\/var\/lib\/postgresql\/data\r\n<\/code><\/pre>\n<p>Modify <code>\/etc\/crowdsec\/config.yaml<\/code>:<\/p>\n<pre><code>db_config:\r\n  type: postgresql\r\n  user: crowdsec\r\n  password: strongpassword\r\n  db_name: crowdsec\r\n  host: db\r\n  port: 5432\r\n<\/code><\/pre>\n<h3>Why PostgreSQL?<\/h3>\n<ul>\n<li>Better concurrency<\/li>\n<li>Durable decision store<\/li>\n<li>Required for multi-node synchronization<\/li>\n<\/ul>\n<p>Generate enrollment key:<\/p>\n<pre><code>cscli bouncers add edge-traefik\r\ncscli machines add edge-node-1\r\n<\/code><\/pre>\n<h3>Step 3: Deploy Traefik on Edge Nodes<\/h3>\n<h3>Docker Compose for Edge Node<\/h3>\n<pre><code>version: \"3.9\"\r\n\r\nservices:\r\n  traefik:\r\n    image: traefik:v3.0\r\n    container_name: traefik\r\n    restart: unless-stopped\r\n    command:\r\n      - --providers.docker=true\r\n      - --entrypoints.web.address=:80\r\n      - --entrypoints.websecure.address=:443\r\n      - --api.dashboard=false\r\n      - --log.level=INFO\r\n      - --accesslog=true\r\n      - --accesslog.filepath=\/logs\/access.log\r\n      - --certificatesresolvers.le.acme.httpchallenge=true\r\n      - --certificatesresolvers.le.acme.httpchallenge.entrypoint=web\r\n      - --certificatesresolvers.le.acme.email=admin@example.com\r\n      - --certificatesresolvers.le.acme.storage=\/letsencrypt\/acme.json\r\n    ports:\r\n      - \"80:80\"\r\n      - \"443:443\"\r\n    volumes:\r\n      - \/var\/run\/docker.sock:\/var\/run\/docker.sock:ro\r\n      - .\/logs:\/logs\r\n      - .\/letsencrypt:\/letsencrypt\r\n    networks:\r\n      - edge\r\n\r\n  crowdsec:\r\n    image: crowdsecurity\/crowdsec:latest\r\n    restart: unless-stopped\r\n    volumes:\r\n      - .\/logs:\/var\/log\/traefik:ro\r\n    environment:\r\n      - COLLECTIONS=crowdsecurity\/traefik\r\n      - ENROLL_KEY=&lt;machine_enroll_key&gt;\r\n      - LAPI_URL=https:\/\/central-lapi.example.com:8080\r\n    networks:\r\n      - edge\r\n\r\nnetworks:\r\n  edge:\r\n<\/code><\/pre>\n<h3>Why enable access logs?<\/h3>\n<p>CrowdSec parses logs to detect:<\/p>\n<ul>\n<li>Layer 7 floods<\/li>\n<li>Excessive 404 scanning<\/li>\n<li>Credential stuffing patterns<\/li>\n<\/ul>\n<h3>Step 4: Configure Traefik CrowdSec Bouncer<\/h3>\n<p>Use the Traefik plugin bouncer.<\/p>\n<p>Add to static config:<\/p>\n<pre><code>experimental:\r\n  plugins:\r\n    crowdsec-bouncer:\r\n      moduleName: github.com\/maxlerebourg\/crowdsec-bouncer-traefik-plugin\r\n      version: v1.4.0\r\n<\/code><\/pre>\n<p>Middleware configuration:<\/p>\n<pre><code>http:\r\n  middlewares:\r\n    crowdsec:\r\n      plugin:\r\n        crowdsec-bouncer:\r\n          crowdsecLapiKey: \"&lt;bouncer_key&gt;\"\r\n          crowdsecLapiHost: \"https:\/\/central-lapi.example.com:8080\"\r\n          crowdsecMode: \"live\"\r\n<\/code><\/pre>\n<p>Attach middleware:<\/p>\n<pre><code>http:\r\n  routers:\r\n    app:\r\n      rule: \"Host(`app.example.com`)\"\r\n      entryPoints:\r\n        - websecure\r\n      service: app-service\r\n      middlewares:\r\n        - crowdsec\r\n<\/code><\/pre>\n<h3>Why live mode?<\/h3>\n<ul>\n<li>Checks decisions at request time<\/li>\n<li>Immediate enforcement<\/li>\n<li>Suitable for fast-moving attacks<\/li>\n<\/ul>\n<h3>Step 5: Protect Origins<\/h3>\n<p>Origins must not be publicly accessible.<\/p>\n<p>Options:<\/p>\n<ul>\n<li>WireGuard tunnels from edge \u2192 origin<\/li>\n<li>Tailscale mesh<\/li>\n<li>Private VPC peering<\/li>\n<\/ul>\n<p>Example WireGuard topology:<\/p>\n<pre><code>[Edge Node] --- WG Tunnel --- [Origin]\r\n<\/code><\/pre>\n<p>Firewall origin to allow traffic only from WireGuard subnet.<\/p>\n<h2>Observability and Debugging<\/h2>\n<h3>Metrics<\/h3>\n<p>Enable Traefik Prometheus metrics:<\/p>\n<pre><code>--metrics.prometheus=true\r\n--metrics.prometheus.addrouterslabels=true\r\n<\/code><\/pre>\n<p>Monitor:<\/p>\n<ul>\n<li>Request rate<\/li>\n<li>4xx\/5xx spikes<\/li>\n<li>Middleware latency<\/li>\n<li>TLS handshake errors<\/li>\n<\/ul>\n<p>CrowdSec metrics:<\/p>\n<pre><code>cscli metrics\r\n<\/code><\/pre>\n<p>Watch for:<\/p>\n<ul>\n<li>Decisions count<\/li>\n<li>Alerts per scenario<\/li>\n<li>API sync failures<\/li>\n<\/ul>\n<h3>Logs<\/h3>\n<p>Key log sources:<\/p>\n<ul>\n<li><code>\/logs\/access.log<\/code> (Traefik)<\/li>\n<li>CrowdSec agent logs<\/li>\n<li>LAPI logs<\/li>\n<\/ul>\n<p>Common issues:<\/p>\n<ul>\n<li>Bouncer key mismatch<\/li>\n<li>Clock drift between nodes<\/li>\n<li>LAPI unreachable (firewall issue)<\/li>\n<li>Incorrect real IP header handling<\/li>\n<\/ul>\n<p>If behind another proxy, set:<\/p>\n<pre><code>--entrypoints.websecure.forwardedHeaders.trustedIPs=0.0.0.0\/0\r\n<\/code><\/pre>\n<p>Or restrict to known ranges.<\/p>\n<h3>Failure Modes<\/h3>\n<ul>\n<li>Central LAPI down \u2192 no new decisions propagate.<\/li>\n<li>Traefik middleware misconfigured \u2192 no enforcement.<\/li>\n<li>Log parsing disabled \u2192 no detection.<\/li>\n<\/ul>\n<p>Mitigation:<\/p>\n<ul>\n<li>Run LAPI in HA mode.<\/li>\n<li>Use health checks.<\/li>\n<li>Add alerting for decision count drops.<\/li>\n<\/ul>\n<h2>Security Considerations<\/h2>\n<h3>Attack Surface<\/h3>\n<p>Edge nodes expose:<\/p>\n<ul>\n<li>80\/443<\/li>\n<li>ACME challenge endpoint<\/li>\n<\/ul>\n<p>Harden:<\/p>\n<ul>\n<li>Disable Traefik dashboard.<\/li>\n<li>Restrict Docker socket access.<\/li>\n<li>Use rootless containers where possible.<\/li>\n<\/ul>\n<h3>Secrets Management<\/h3>\n<p>Avoid plaintext keys in Compose files.<\/p>\n<p>Use:<\/p>\n<ul>\n<li>Docker secrets<\/li>\n<li>SOPS + GitOps<\/li>\n<li>Environment variables injected at runtime<\/li>\n<\/ul>\n<p>Rotate:<\/p>\n<ul>\n<li>Bouncer keys<\/li>\n<li>Enrollment keys<\/li>\n<li>ACME email credentials<\/li>\n<\/ul>\n<h3>Network Isolation<\/h3>\n<ul>\n<li>Origin accessible only via tunnel.<\/li>\n<li>LAPI accessible only from edge IP ranges.<\/li>\n<li>SSH restricted via firewall + fail2ban.<\/li>\n<\/ul>\n<p>Implement nftables:<\/p>\n<pre><code>nft add rule inet filter input tcp dport {22} ip saddr {trusted_ip} accept\r\nnft add rule inet filter input drop\r\n<\/code><\/pre>\n<h2>Performance Considerations<\/h2>\n<h3>Bottlenecks<\/h3>\n<ul>\n<li>TLS handshakes<\/li>\n<li>Middleware latency<\/li>\n<li>Central LAPI API calls<\/li>\n<li>Disk I\/O from logging<\/li>\n<\/ul>\n<p>Mitigation:<\/p>\n<ul>\n<li>Enable HTTP\/3 in Traefik.<\/li>\n<li>Use SSD storage.<\/li>\n<li>Set log rotation.<\/li>\n<li>Increase file descriptor limits.<\/li>\n<\/ul>\n<h3>Scaling Strategy<\/h3>\n<p>Horizontal scaling:<\/p>\n<ul>\n<li>Add more edge VPS nodes.<\/li>\n<li>Use GeoDNS or anycast.<\/li>\n<\/ul>\n<p>Vertical scaling:<\/p>\n<ul>\n<li>Increase CPU for TLS-heavy workloads.<\/li>\n<li>Enable OpenSSL acceleration.<\/li>\n<\/ul>\n<p>CrowdSec scaling:<\/p>\n<ul>\n<li>Dedicated PostgreSQL cluster.<\/li>\n<li>LAPI load-balanced behind HAProxy.<\/li>\n<\/ul>\n<h3>Resource Optimization<\/h3>\n<p>Disable unnecessary features:<\/p>\n<ul>\n<li>Traefik dashboard<\/li>\n<li>Excess debug logging<\/li>\n<\/ul>\n<p>Tune:<\/p>\n<pre><code>sysctl -w net.core.somaxconn=65535\r\nsysctl -w net.ipv4.tcp_tw_reuse=1\r\n<\/code><\/pre>\n<p>Enable connection reuse upstream.<\/p>\n<h2>Tradeoffs and Design Decisions<\/h2>\n<h3>Why Traefik?<\/h3>\n<ul>\n<li>Native Docker integration<\/li>\n<li>Dynamic configuration<\/li>\n<li>Middleware ecosystem<\/li>\n<li>ACME automation<\/li>\n<\/ul>\n<p>Alternatives:<\/p>\n<ul>\n<li>Nginx (more manual config)<\/li>\n<li>Caddy (simpler but fewer advanced routing controls)<\/li>\n<li>HAProxy (excellent performance but steeper config curve)<\/li>\n<\/ul>\n<h3>Why CrowdSec?<\/h3>\n<ul>\n<li>Behavioral detection vs static rule WAF<\/li>\n<li>Shared threat intelligence<\/li>\n<li>API-based ban propagation<\/li>\n<li>Lightweight footprint<\/li>\n<\/ul>\n<p>Alternative:<\/p>\n<ul>\n<li>ModSecurity (heavyweight, rule-based)<\/li>\n<li>Fail2ban (local-only, not distributed)<\/li>\n<\/ul>\n<h3>What to Avoid<\/h3>\n<ul>\n<li>Exposing origin directly \u201ctemporarily\u201d<\/li>\n<li>Running LAPI on edge nodes<\/li>\n<li>Sharing Docker socket broadly<\/li>\n<li>Ignoring time synchronization (use chrony)<\/li>\n<\/ul>\n<h2>Conclusion<\/h2>\n<p>A self-hosted global edge WAF architecture built with Traefik and CrowdSec provides:<\/p>\n<ul>\n<li>Distributed DDoS resistance<\/li>\n<li>Centralized ban intelligence<\/li>\n<li>Full routing control<\/li>\n<li>Cost-efficient global presence<\/li>\n<li>Cloud-provider independence<\/li>\n<\/ul>\n<p>By combining globally distributed edge nodes with centralized behavioral intelligence, infrastructure teams can build a production-grade defensive perimeter without relying on proprietary CDN platforms.<\/p>\n<p>This architecture scales horizontally, enforces security decisions consistently across regions, and integrates cleanly into modern containerized workflows. With careful hardening, observability, and performance tuning, it delivers a resilient and transparent edge layer suitable for serious production workloads in 2026.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Public-facing services in 2026 operate in a hostile environment. Automated scanners, credential stuffing bots, layer 7 DDoS floods, and exploit kits continuously target exposed&hellip;<\/p>\n","protected":false},"author":1,"featured_media":5883,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[757],"tags":[],"class_list":["post-5882","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-networking"],"_links":{"self":[{"href":"https:\/\/toolswift.com\/blog\/wp-json\/wp\/v2\/posts\/5882","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/toolswift.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/toolswift.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/toolswift.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/toolswift.com\/blog\/wp-json\/wp\/v2\/comments?post=5882"}],"version-history":[{"count":1,"href":"https:\/\/toolswift.com\/blog\/wp-json\/wp\/v2\/posts\/5882\/revisions"}],"predecessor-version":[{"id":5884,"href":"https:\/\/toolswift.com\/blog\/wp-json\/wp\/v2\/posts\/5882\/revisions\/5884"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/toolswift.com\/blog\/wp-json\/wp\/v2\/media\/5883"}],"wp:attachment":[{"href":"https:\/\/toolswift.com\/blog\/wp-json\/wp\/v2\/media?parent=5882"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/toolswift.com\/blog\/wp-json\/wp\/v2\/categories?post=5882"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/toolswift.com\/blog\/wp-json\/wp\/v2\/tags?post=5882"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}