CodeNewbie Community 🌱

Sharon428931
Sharon428931

Posted on

Building a Docker-Based Web Defense with SafeLine WAF and Triple Proxy Setup

Introduction

"To defend, you must first understand the attack. Without knowing how to protect, how can you expect to break through?"

As a beginner learning penetration testing, I’ve realized that defensive thinking is essential even for attackers. When switching roles to a site administrator, the core question becomes: How can I maximize website protection at the lowest possible cost?

So I ran an experiment using CDN, Nginx, SafeLine WAF, and Docker to build a practical and budget-friendly defense stack.


Goal

Use Docker containers for everything and set up a full traffic flow:

User → www.example.com → Cloudflare (CDN) 
→ Nginx Proxy Manager (Reverse Proxy)
→ SafeLine WAF (Traffic inspection & protection)
→ Local App (Docker container)
Enter fullscreen mode Exit fullscreen mode


Prerequisites

Install the following components on your server:

Core Components

  • Docker + Docker Compose
  • SafeLine WAF

    Developed by Chaitin Tech over a decade, SafeLine is a next-gen WAF with 17.1k stars on GitHub. No need to explain—just results.

    🔧 Install Guide

    🔗 Web Panel: https://<your-ip>:9443

  • Nginx Proxy Manager (NPM)

    A GUI tool to manage reverse proxies & SSL certs in one place

    🌐 Official Site
    🔗 Web Panel: http://<your-ip>:81


Step-by-Step Setup


Step 1: Deploy Test Website (Nginx in Docker)

Pull Nginx container:

docker pull nginx
Enter fullscreen mode Exit fullscreen mode

Create configuration folders and files:
(Before deploying the Nginx Docker container, it's important to first create the necessary configuration directories (such as config files, static content, and log folders) on the host machine. These will be mounted into the container at runtime, allowing for flexible control and persistent data management.)

mkdir -p ~/nginx/{conf,html,logs}
cd ~/nginx
Enter fullscreen mode Exit fullscreen mode

Create Nginx config:

vim ~/nginx/conf/nginx.conf
Enter fullscreen mode Exit fullscreen mode

Paste the following:

user  nginx;
worker_processes  auto;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;

events {
    worker_connections  1024;
}

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    server_tokens off;

    log_format main '$remote_addr - $request';
    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    keepalive_timeout  65;

    server {
        listen 12080;  # Important! Use a custom port (NOT 80) for SafeLine
        server_name _;

        root   /usr/share/nginx/html;
        index  index.html;

        location / {
            try_files $uri $uri/ =404;
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Make any necessary changes to the config, and don’t forget to save it.

Upload HTML files to ~/nginx/html/.

Start the container:

docker run -d --name my-nginx \
  -p 12080:12080 \
  -v $(pwd)/conf/nginx.conf:/etc/nginx/nginx.conf:ro \
  -v $(pwd)/html:/usr/share/nginx/html \
  -v $(pwd)/logs:/var/log/nginx \
  nginx:alpine
Enter fullscreen mode Exit fullscreen mode

That’s it for the web setup!

Try visiting http://<your-ip>:<your-port> in your browser.

If the page loads, everything is working as expected.


Step 2: Configure Nginx Proxy Manager (NPM)

SSL Certificate via Cloudflare

  1. Go to SSL Certificates > Add SSL Certificate

  1. Enter Domain Names: *.example.com
  2. Check Use DNS Challenge
  3. Choose Cloudflare as provider
  4. In Credentials File Content, paste your Cloudflare API Token
  5. Agree to Let's Encrypt TOS
  6. Click Save

Wait until the wildcard cert is issued.

Add Proxy Host

Go to: Dashboard → Proxy Hosts → Add Proxy Host

  • Domain Names: www.example.com
  • Scheme: http (This is for internal forwarding, not external access)
  • Forward Hostname/IP: 172.17.0.1 (Use ip addr show docker0 to confirm; most likely it's this)
  • Forward Port: Use a random unused port, e.g., 10080

⚠️ Since the stack is:

NPM → SafeLine → Web Service,

SafeLine will listen on this random port and forward to Nginx (port 12080).

Here’s how I configured it:

Attach SSL:

  • Tab: SSL
  • Choose the cert you just created
  • Enable Force SSL
  • Click Save


Step 3: Configure SafeLine WAF

Go to Applications → Add Application

  • Domain: Your full domain (e.g. www.example.com)
  • Port: The NPM forwarding port (10080)
  • SSL: Leave unchecked
  • Upstream Server: http://127.0.0.1:12080 (local Nginx port)
  • App Name: Any name you like

Click Submit.

Here’s how I configured it:

To properly get the real client IP:

Go to Applications → Advanced Config → Get Attack IP From

Choose "The 2nd Rightmost IP In X-Forwarded-For "

(Since Cloudflare and NPM both act as proxies, the 2nd Rightmost IP is the attacker’s real IP)


Step 4: Secure Ports with iptables

Until now, the Nginx container's port is still exposed. An attacker can bypass your domain and scan open IP:PORT directly. Let’s fix that.

At this stage, both my domain and the exposed IP:port are still accessible from the public internet.

Source Code

Test site source:

🔗 github.com/imbyter/homepage

Understanding iptables Rule Types and Matching Logic

The port restriction via iptables in this setup can be divided into two categories:

1. Restrictions on Docker-exposed ports:

  • Managed via the FORWARD chain
  • Managed via the DOCKER-USER chain

2. Restrictions on locally hosted ports:

  • Managed via the INPUT chain

Rule Matching Logic

  • iptables rules are matched top to bottom, in the order they are written.
  • Always add accept rules first for trusted traffic.
  • Drop rules should come last as a fallback to block any unmatched packets.
  • Use numeric rule positions (e.g., -I CHAIN NUM) to precisely control execution order.

🔒 FORWARD Chain (Docker)

# Allow local host (127.0.0.1) to access port 12080
iptables -I FORWARD 1 -s 127.0.0.1 -p tcp --dport 12080 -j ACCEPT

# Allow traffic from Docker bridge network (default: 172.17.0.0/16)
iptables -I FORWARD 2 -s 172.17.0.0/16 -p tcp --dport 12080 -j ACCEPT

# Drop all other traffic to port 12080
iptables -A FORWARD -p tcp --dport 12080 -j DROP
Enter fullscreen mode Exit fullscreen mode

🔒 DOCKER-USER Chain (Docker)

# Allow local host to access port 12080
iptables -I DOCKER-USER 1 -s 127.0.0.1 -p tcp --dport 12080 -j ACCEPT

# Allow Docker bridge network to access port 12080
iptables -I DOCKER-USER 2 -s 172.17.0.0/16 -p tcp --dport 12080 -j ACCEPT

# Drop all other traffic to port 12080
iptables -A DOCKER-USER -p tcp --dport 12080 -j DROP
Enter fullscreen mode Exit fullscreen mode

🔒 INPUT Chain (Local)

# Allow local host to access port 12080
iptables -I INPUT 1 -s 127.0.0.1 -p tcp --dport 12080 -j ACCEPT

# Allow Docker bridge network to access port 12080
iptables -I INPUT 2 -s 172.17.0.0/16 -p tcp --dport 12080 -j ACCEPT

# Drop all other traffic to port 12080
iptables -A INPUT -p tcp --dport 12080 -j DROP
Enter fullscreen mode Exit fullscreen mode

Testing the Setup

Cloudflare Test

Try pinging your domain:

NPM + iptables Test

  • Domain access: ✅ Allowed

  • IP + port access: ❌ Blocked

SafeLine WAF Test

  • Normal access: ✅ Allowed

  • Simulated attack: 🚫 Blocked

Success! Protection is active.


Final Thoughts

With just Docker, Cloudflare, Nginx Proxy Manager, and SafeLine WAF, I’ve built a solid, layered security setup that can handle real-world attacks—all without spending a penny.

If you're running a homelab or building self-hosted services, this setup gives you serious defense on a budget.


Join the SafeLine Community

Top comments (0)