WAF Reverse Proxy
    NGINX + ModSecurity

    Deploy BunkerWeb on a VPS

    Self-host BunkerWeb, an NGINX-based reverse proxy with built-in ModSecurity WAF, OWASP CRS, and Let's Encrypt automation, on a RamNode VPS via Docker Compose.

    BunkerWeb is an open-source, NGINX-based reverse proxy with a built-in WAF (ModSecurity + OWASP CRS, bot/anti-bruteforce protection, Let's Encrypt automation, etc.). This guide uses the Docker Compose deployment method, which is the most portable and easiest to maintain.

    Target: Ubuntu 24.04 LTS, sudo/root SSH access.

    1. Prerequisites

    • A RamNode VPS with at least 1-2 vCPU / 2GB RAM
    • Docker & Docker Compose plugin
    • Ports 80/443 free for BunkerWeb to bind to (don't run another web server directly on these ports)
    • DNS for the site(s) you want to protect pointed at this VPS

    2. Install Docker

    shell
    curl -fsSL https://get.docker.com | sudo sh
    sudo systemctl enable --now docker

    Confirm the Compose plugin is available:

    shell
    docker compose version

    3. Create a project directory

    shell
    mkdir -p ~/bunkerweb && cd ~/bunkerweb

    4. Create the docker-compose.yml

    BunkerWeb's stack has three main components: bunkerweb (the proxy itself), bw-scheduler (applies config changes), and bw-ui (web admin interface) plus a database.

    shell
    nano docker-compose.yml

    Paste (adjust BUNKERWEB_VERSION to the current stable release, and pick strong secrets):

    shell
    x-bw-env: &bw-env
      API_WHITELIST_IP: "127.0.0.1 10.20.30.0/24"   # internal Docker network range used by the scheduler/UI
    
    services:
      bunkerweb:
        image: bunkerity/bunkerweb:1.6
        ports:
          - "80:8080/tcp"
          - "443:8443/tcp"
          - "443:8443/udp"   # for HTTP/3 / QUIC
        environment:
          <<: *bw-env
        restart: unless-stopped
        networks:
          - bw-universe
          - bw-services
    
      bw-scheduler:
        image: bunkerity/bunkerweb-scheduler:1.6
        depends_on:
          - bunkerweb
        environment:
          <<: *bw-env
          DATABASE_URI: "mariadb+pymysql://bunkerweb:${DB_PASSWORD}@bw-db:3306/db"
          BUNKERWEB_INSTANCES: "bunkerweb"
          SERVER_NAME: ""
          MULTISITE: "yes"
          UI_HOST: "http://bw-ui:7000"
        volumes:
          - bw-data:/data
        restart: unless-stopped
        networks:
          - bw-universe
    
      bw-ui:
        image: bunkerity/bunkerweb-ui:1.6
        depends_on:
          - bw-scheduler
        environment:
          <<: *bw-env
          DATABASE_URI: "mariadb+pymysql://bunkerweb:${DB_PASSWORD}@bw-db:3306/db"
          ADMIN_USERNAME: "admin"
          ADMIN_PASSWORD: "${UI_ADMIN_PASSWORD}"
        restart: unless-stopped
        networks:
          - bw-universe
    
      bw-db:
        image: mariadb:11
        environment:
          MYSQL_RANDOM_ROOT_PASSWORD: "yes"
          MYSQL_DATABASE: "db"
          MYSQL_USER: "bunkerweb"
          MYSQL_PASSWORD: "${DB_PASSWORD}"
        volumes:
          - bw-db-data:/var/lib/mysql
        restart: unless-stopped
        networks:
          - bw-universe
    
    volumes:
      bw-data:
      bw-db-data:
    
    networks:
      bw-universe:
        name: bw-universe
      bw-services:
        name: bw-services

    Check the BunkerWeb docs for the current recommended compose file for the version you're deploying — the exact service list/env vars shift slightly between releases.

    5. Create a .env file for secrets

    shell
    nano .env
    shell
    DB_PASSWORD=change_this_to_a_strong_password
    UI_ADMIN_PASSWORD=change_this_too

    6. Open the firewall

    shell
    sudo ufw allow 80/tcp
    sudo ufw allow 443/tcp
    sudo ufw allow 443/udp

    The bw-ui admin interface is not exposed externally by default in this layout — you'll reach it through a protected site entry (step 8) rather than a raw port, which is the safer setup.

    7. Start the stack

    shell
    docker compose up -d
    docker compose ps

    Give it a minute — the scheduler needs to initialize the database and generate the first NGINX config.

    8. Expose the admin UI as a protected site

    Rather than opening a separate port, the standard approach is to add the UI itself as a BunkerWeb-protected site so it gets HTTPS + WAF coverage. In practice:

    1. Point a subdomain (e.g. bunkerweb-ui.example.com) at your VPS IP
    2. Temporarily set SERVER_NAME and site-specific env vars for bw-ui's domain in the scheduler config (or via the UI's setup wizard on first run, depending on version — recent BunkerWeb releases include a first-run setup wizard reachable directly once the UI container is up)
    3. Follow the in-UI wizard to finish bootstrapping, set the admin account, and enable Let's Encrypt for that domain

    Refer to the "Quickstart guide" in the official docs for the exact current flow, since the UI onboarding has changed across versions.

    9. Add your first protected application

    Once logged into the UI:

    1. Services > New Service
    2. Enter the domain name (e.g. app.example.com)
    3. Set REVERSE_PROXY_HOST to your backend, e.g. http://127.0.0.1:8080 or another container/IP
    4. Enable AUTO_LETS_ENCRYPT if you want automatic HTTPS certs for the domain
    5. Save — the scheduler pushes the new config to the bunkerweb proxy container automatically

    10. Tune protections

    Under each service's settings you can toggle:

    • ModSecurity + OWASP Core Rule Set
    • Antibot challenges (CAPTCHA, JS challenge, etc.)
    • Rate limiting
    • Country/IP allow-deny lists
    • Bad bot / scanner blocking

    11. Updating BunkerWeb

    shell
    cd ~/bunkerweb
    docker compose pull
    docker compose up -d

    Check the release notes before bumping major versions — schema/env var changes do happen between minor version lines.

    12. Backup

    Back up:

    • ~/bunkerweb/docker-compose.yml and .env
    • The bw-data and bw-db-data Docker volumes (docker volume inspect to find their mount points, or use docker run --rm -v bw-db-data:/data ... tar to snapshot them)