WafWay Documentation

Welcome to WafWay documentation. WafWay is an enterprise-grade Web Application Firewall that protects your applications from SQL injection, XSS, and other OWASP Top 10 threats.

New to WafWay? Start with our Quick Start guide to get up and running in 5 minutes.

Quick Start

Get WafWay running in just a few commands:

1. Download the Binary

# Using curl
curl -sSL https://get.wafway.io | sh

# Or download directly
wget https://github.com/wafway/wafway/releases/latest/download/wafway-linux-amd64
chmod +x wafway-linux-amd64
sudo mv wafway-linux-amd64 /usr/local/bin/wafway

2. Create Configuration

# Create config directory
sudo mkdir -p /etc/wafway

# Create basic config
sudo tee /etc/wafway/wafway.yaml << 'EOF'
edition: community

server:
  listen_addr: "0.0.0.0"
  http_port: 80
  admin_port: 8080

backend:
  url: "http://127.0.0.1:3000"  # Your application
  timeout: 30s

protection:
  mode: "medium"
  sqli:
    enabled: true
  xss:
    enabled: true
  rate_limit:
    enabled: true
    requests_per_minute: 100

dashboard:
  enabled: true
  username: "admin"
EOF

3. Start WafWay

# Run directly
sudo wafway --config /etc/wafway/wafway.yaml

# Or as a service
sudo systemctl enable wafway
sudo systemctl start wafway

4. Access Dashboard

Open your browser and navigate to http://your-server:8080 to access the dashboard.

System Requirements

Requirement Minimum Recommended
Operating System Linux (kernel 3.10+) Ubuntu 22.04, AlmaLinux 9
CPU 1 core 2+ cores
RAM 512 MB 1 GB+
Disk Space 100 MB 1 GB+ (for logs)
Network Ports 80, 443, 8080 80, 443, 8080

Linux Installation

WafWay supports all major Linux distributions including Ubuntu, Debian, CentOS, RHEL, and AlmaLinux.

Using the Install Script (Recommended)

curl -sSL https://get.wafway.io | sh

This script will:

Manual Installation

# Create service user
sudo useradd --system --no-create-home --shell /sbin/nologin wafway

# Create directories
sudo mkdir -p /opt/wafway /etc/wafway /var/log/wafway /var/lib/wafway

# Download binary
sudo wget -O /opt/wafway/wafway \
    https://github.com/wafway/wafway/releases/latest/download/wafway-linux-amd64

# Set permissions
sudo chmod 755 /opt/wafway/wafway
sudo chown -R wafway:wafway /var/log/wafway /var/lib/wafway

# Create symlink
sudo ln -sf /opt/wafway/wafway /usr/local/bin/wafway

Docker Installation

# Quick start with Docker
docker run -d \
    -p 80:80 \
    -p 8080:8080 \
    -e BACKEND_URL=http://host.docker.internal:3000 \
    wafway/waf:latest

# With docker-compose
version: '3.8'
services:
  waf:
    image: wafway/waf:latest
    ports:
      - "80:80"
      - "8080:8080"
    volumes:
      - ./wafway.yaml:/etc/wafway/wafway.yaml
      - waf-data:/var/lib/wafway
    restart: unless-stopped

volumes:
  waf-data:

Standalone Mode (No Nginx Required)

WafWay is a fully self-contained reverse proxy with built-in TLS, multi-domain routing, and security headers. It does not require Nginx, Apache, or any other proxy.

When to use standalone: New deployments, simple architectures, or when you want the fewest moving parts. WafWay handles SSL termination, inspection, and proxying — all in one binary.

Architecture: Standalone with SSL

# Traffic flow: Internet → WafWay :443 (SSL + WAF) → App Server

# No Nginx, no HAProxy, no Caddy — just WafWay.

   Client (HTTPS)
       |
       v
   WafWay (:443)
   |- TLS termination (your cert + key)
   |- WAF inspection (900+ patterns)
   |- Security headers (HSTS, CSP, X-Frame)
   |- Multi-domain routing
       |
       v
   App Server (:3000) ← private, localhost only

Configuration: Standalone with SSL

# /etc/wafway/wafway.yaml (standalone mode)
server:
  listen_addr: "0.0.0.0"
  http_port: 80           # Redirect to HTTPS (optional)
  https_port: 443         # Public-facing port
  tls:
    enabled: true
    cert_file: "/etc/wafway/ssl/fullchain.pem"
    key_file: "/etc/wafway/ssl/privkey.pem"
    min_version: "TLS1.2"

# Single backend
backend:
  url: "http://127.0.0.1:3000"
  timeout: 30s

# Or multi-domain routing
backends:
  - hosts: ["app.example.com", "www.example.com"]
    url: "http://127.0.0.1:3000"
  - hosts: ["api.example.com"]
    url: "http://127.0.0.1:4000"
  - hosts: ["admin.example.com"]
    url: "http://127.0.0.1:5000"

# Everything else works the same
protection:
  mode: "medium"
  sqli: { enabled: true }
  xss: { enabled: true }
  # ...

Configuration: Standalone without SSL (HTTP only)

# For internal/staging or when SSL is handled by a cloud load balancer
server:
  listen_addr: "0.0.0.0"
  http_port: 8081         # WAF listens here

backend:
  url: "http://127.0.0.1:3000"

SSL Certificate Setup (Let's Encrypt)

# Install certbot
sudo apt install certbot    # Ubuntu/Debian
sudo yum install certbot    # CentOS/RHEL

# Get certificate (stop WafWay briefly for port 80)
sudo systemctl stop wafway
sudo certbot certonly --standalone -d app.example.com -d api.example.com

# Certificate files will be at:
#   /etc/letsencrypt/live/app.example.com/fullchain.pem
#   /etc/letsencrypt/live/app.example.com/privkey.pem

# Update wafway.yaml:
server:
  tls:
    enabled: true
    cert_file: "/etc/letsencrypt/live/app.example.com/fullchain.pem"
    key_file: "/etc/letsencrypt/live/app.example.com/privkey.pem"

# Start WafWay
sudo systemctl start wafway

# Auto-renew (add to crontab)
# Certbot renew requires port 80 — use webroot or DNS challenge
0 3 * * * certbot renew --deploy-hook "systemctl restart wafway"

What WafWay Handles in Standalone Mode

FeatureStandalone (No Nginx)With Nginx
SSL/TLS termination✓ Built-in (cert + key)Nginx handles
HTTP/2✓ Automatic with TLSNginx handles
Multi-domain routing✓ Host-based backends✓ Either can route
Security headers✓ HSTS, CSP, X-Frame, etc.✓ Either can set
WAF inspection✓ All 900+ patterns✓ Same
WebSocket proxy✓ Built-in passthroughNeed Nginx WS config too
Static file serving✗ Proxy to backend only✓ Nginx serves directly
Auto cert renewal✗ Manual or cron✓ Certbot + Nginx plugin
Load balancing WAF✗ Single instance✓ Nginx balances to N WAFs

Recommendation: Use standalone mode for simplicity (fewer components, easier debugging). Add Nginx only if you need static file serving, auto-cert renewal, or load balancing across multiple WafWay instances.

Deployment with Nginx

If you already have Nginx or need its additional features, WafWay integrates in two patterns:

Pattern A: Nginx in Front (SSL at Nginx) — Most Common

# Traffic flow: Internet → Nginx :443 (SSL) → WafWay :8081 (WAF) → App :3000

# Best when:
# - You already have Nginx + certbot managing SSL
# - You want Nginx to serve static files directly
# - You want Nginx to load-balance across multiple WafWay instances

# ---- Nginx config (/etc/nginx/sites-enabled/app.conf) ----

# HTTP → HTTPS redirect
server {
    listen 80;
    server_name app.example.com api.example.com;
    return 301 https://$host$request_uri;
}

# HTTPS → WafWay
server {
    listen 443 ssl http2;
    server_name app.example.com api.example.com;

    ssl_certificate     /etc/letsencrypt/live/app.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/app.example.com/privkey.pem;

    # Forward all traffic to WafWay
    location / {
        proxy_pass http://127.0.0.1:8081;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # WebSocket support
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";

        # Timeouts
        proxy_connect_timeout 10s;
        proxy_read_timeout 60s;
        proxy_send_timeout 60s;
    }
}

# ---- WafWay config (/etc/wafway/wafway.yaml) ----
server:
  listen_addr: "127.0.0.1"   # Only accept from localhost (Nginx)
  http_port: 8081

backend:
  url: "http://127.0.0.1:3000"

# Multi-domain routing (WafWay routes by Host header)
backends:
  - hosts: ["app.example.com"]
    url: "http://127.0.0.1:3000"
  - hosts: ["api.example.com"]
    url: "http://127.0.0.1:4000"

Pattern B: WafWay in Front (SSL at WafWay)

# Traffic flow: Internet → WafWay :443 (SSL + WAF) → Nginx :8080 → App :3000

# Best when:
# - You want WafWay to see raw HTTPS requests
# - You want WafWay to handle TLS fingerprinting (future)
# - New setup without existing Nginx SSL config

# ---- WafWay config ----
server:
  listen_addr: "0.0.0.0"
  https_port: 443
  tls:
    enabled: true
    cert_file: "/etc/wafway/ssl/fullchain.pem"
    key_file: "/etc/wafway/ssl/privkey.pem"

backend:
  url: "http://127.0.0.1:8080"   # Points to Nginx

# ---- Nginx config ----
server {
    listen 8080;
    server_name app.example.com;

    # Trust WafWay headers
    set_real_ip_from 127.0.0.1;
    real_ip_header X-Forwarded-For;

    # Serve static files directly (Nginx is faster for this)
    location /static/ {
        root /var/www/app;
    }

    # Proxy dynamic requests to app
    location / {
        proxy_pass http://127.0.0.1:3000;
    }
}

Pattern C: Nginx Load Balancing Multiple WafWay Instances

# Traffic flow: Internet → Nginx :443 (SSL + LB) → WafWay-1/2/3 :8081 → App

# Best for high-availability / high-traffic deployments

# ---- Nginx config ----
upstream wafway_pool {
    least_conn;
    server 127.0.0.1:8081;    # WafWay instance 1
    server 127.0.0.1:8082;    # WafWay instance 2
    server 127.0.0.1:8083;    # WafWay instance 3
}

server {
    listen 443 ssl http2;
    server_name app.example.com;

    ssl_certificate     /etc/letsencrypt/live/app.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/app.example.com/privkey.pem;

    location / {
        proxy_pass http://wafway_pool;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

# NOTE: When running multiple instances, enable Redis for shared state:
# In each WafWay's wafway.yaml:
cluster:
  enabled: true
  redis:
    enabled: true
    address: "127.0.0.1:6379"

Which Pattern Should I Choose?

ScenarioRecommendedWhy
New setup, no existing NginxStandaloneFewest components, simplest to maintain
Already have Nginx + certbotPattern A (Nginx in front)Keep existing SSL setup, add WAF behind it
Need static file servingPattern B (WafWay in front)WafWay inspects, Nginx serves static files
High traffic / HA requiredPattern C (Nginx LB)Nginx load-balances across WAF instances + Redis
Cloud with ALB/NLBStandalone (LB handles SSL)Cloud LB terminates SSL, WafWay on HTTP :8081
KubernetesK8s DeploymentIngress controller replaces Nginx

Basic Configuration

The main configuration file is located at /etc/wafway/wafway.yaml.

# Complete configuration example
edition: enterprise

server:
  listen_addr: "0.0.0.0"
  http_port: 80
  https_port: 443
  read_timeout: 30s
  write_timeout: 30s

backend:
  url: "http://127.0.0.1:8000"
  timeout: 30s
  health_check_path: "/health"
  health_check_interval: 30s

protection:
  mode: "medium"  # paranoid, high, medium, low, detect

dashboard:
  enabled: true
  port: 8080
  username: "admin"

logging:
  level: "info"
  file: "/var/log/wafway/waf.log"

Protection Modules

WafWay includes multiple protection modules that can be individually enabled and configured:

Module Description Default
sqli SQL Injection detection Enabled
xss Cross-Site Scripting prevention Enabled
command_inj Command Injection detection Enabled
path_traversal Path Traversal attacks Enabled
ssrf Server-Side Request Forgery Enabled
xxe XML External Entity Enabled
bot Bot detection and blocking Enabled
scanner Vulnerability scanner detection Enabled

Rate Limiting

Configure rate limiting to prevent abuse:

rate_limit:
  enabled: true
  requests_per_minute: 100
  window_size: 60
  ban_duration: 10m
  whitelist:
    - "10.0.0.0/8"      # Internal network
    - "192.168.0.0/16"  # Private network

Geo Blocking

Block or allow traffic by country:

geo_block:
  enabled: true
  geoip_path: "/etc/wafway/GeoLite2-Country.mmdb"
  blocked_countries:
    - "XX"  # Unknown
  allowed_countries: []  # Empty = allow all except blocked
  block_tor: true
  block_vpns: false

Accessing Dashboard

The dashboard is available at http://your-server:8080 by default. On first login:

  1. Enter the username (default: admin)
  2. Enter your password (must be set up via the hashpass tool)
  3. The password is securely hashed using bcrypt

Security Note: For production, restrict dashboard access to trusted IPs or use SSH tunneling.

Password Setup

WafWay uses industry-standard bcrypt password hashing. Use the included hashpass tool to generate password hashes:

# Generate a password hash
./hashpass "YourSecurePassword123!"

# Output:
# Password hash generated successfully!
# Add this to your wafway.yaml configuration:
#
# dashboard:
#   password_hash: "$2a$12$xxx..."

Add the generated hash to your configuration:

dashboard:
  enabled: true
  username: "admin"
  password_hash: "$2a$12$Z8IZk0PNoxgQm9UUNwESi.eHEgKrQKPlB9UiIzrBZSg8NiNQgZQ.O"

Password Requirements: Passwords must contain at least 8 characters with uppercase, lowercase, numbers, and special characters.

Secure Tokens

WafWay generates cryptographically secure session tokens using Go's crypto/rand package. Tokens are:

Security Headers

WafWay automatically adds essential security headers to all responses to protect against common web vulnerabilities. These headers are configurable and enabled by default.

Default Headers: WafWay automatically sets X-Content-Type-Options, X-Frame-Options, X-XSS-Protection, Referrer-Policy, and Permissions-Policy headers on all proxied responses.

Header Default Value Purpose
X-Content-Type-Options nosniff Prevents MIME type sniffing attacks
X-Frame-Options SAMEORIGIN Prevents clickjacking attacks
X-XSS-Protection 1; mode=block Enables browser XSS filter
Referrer-Policy strict-origin-when-cross-origin Controls referrer information leakage
Permissions-Policy geolocation=(), microphone=(), camera=() Restricts browser features

WafWay also automatically removes identifying headers from backend responses:

HSTS Configuration

HTTP Strict Transport Security (HSTS) forces browsers to only connect via HTTPS, preventing downgrade attacks and cookie hijacking. WafWay supports full HSTS configuration.

# HSTS Configuration in wafway.yaml
security_headers:
  hsts:
    enabled: true              # Enable HSTS header
    max_age: 63072000          # 2 years (recommended)
    include_subdomains: true   # Apply to all subdomains
    preload: false             # Enable for HSTS preload list submission

HSTS Options

Option Default Description
enabled true Enable or disable HSTS header
max_age 63072000 Time in seconds browsers should remember HTTPS-only (2 years recommended)
include_subdomains true Apply HSTS to all subdomains
preload false Add preload directive for browser preload lists

Warning: Before enabling preload, ensure your entire domain and all subdomains support HTTPS. Once added to preload lists, removal is difficult and can take months.

Example Output

# With default settings, WafWay adds:
Strict-Transport-Security: max-age=63072000; includeSubDomains

# With preload enabled:
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload

Content Security Policy (CSP)

Content Security Policy helps prevent XSS, clickjacking, and code injection attacks by specifying which content sources are allowed. WafWay provides comprehensive CSP configuration.

# CSP Configuration in wafway.yaml
security_headers:
  csp:
    enabled: true
    default_src: ["'self'"]
    script_src: ["'self'"]
    style_src: ["'self'", "'unsafe-inline'"]
    img_src: ["'self'", "data:", "https:"]
    font_src: ["'self'", "https://fonts.gstatic.com"]
    connect_src: ["'self'", "https://api.example.com"]
    frame_ancestors: ["'none'"]
    form_action: ["'self'"]
    base_uri: ["'self'"]
    object_src: ["'none'"]
    report_uri: "/csp-report"    # Optional: endpoint for violation reports
    report_only: false           # Set true to test without blocking

CSP Directives

Directive Default Description
default_src ['self'] Fallback for other directives
script_src ['self'] Allowed JavaScript sources
style_src ['self', 'unsafe-inline'] Allowed CSS sources
img_src ['self', 'data:'] Allowed image sources
font_src ['self'] Allowed font sources
connect_src ['self'] Allowed XHR/WebSocket/fetch targets
frame_ancestors ['none'] Who can embed this page (clickjacking protection)
form_action ['self'] Allowed form submission targets
base_uri ['self'] Allowed base URL for relative URLs
object_src ['none'] Allowed plugin sources (Flash, Java)

CSP Source Values

Testing CSP: Set report_only: true to test your CSP policy without breaking your site. Violations will be logged but not blocked.

Example Output

# WafWay generates a CSP header like:
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-ancestors 'none'; form-action 'self'; base-uri 'self'; object-src 'none'

CORS Whitelist Configuration

Cross-Origin Resource Sharing (CORS) controls which domains can make requests to your API. WafWay uses a whitelist approach for security - no wildcards allowed.

Security Note: WafWay does NOT support wildcard (*) CORS origins. You must explicitly list allowed origins. This prevents unauthorized cross-origin access to your admin API.

# CORS Configuration for Admin Portal in wafway.yaml
admin_portal:
  enabled: true
  listen: ":9090"
  cors:
    allowed_origins:
      - "https://admin.example.com"
      - "https://dashboard.example.com"
      - "http://localhost:3000"        # For local development
    allowed_methods:
      - "GET"
      - "POST"
      - "PUT"
      - "DELETE"
      - "OPTIONS"
    allowed_headers:
      - "Accept"
      - "Authorization"
      - "Content-Type"
      - "X-Requested-With"
    exposed_headers: []                # Headers browser can access
    allow_credentials: true            # Allow cookies/auth headers
    max_age: 86400                      # Preflight cache: 24 hours

CORS Options

Option Default Description
allowed_origins [] (same-origin only) List of allowed origins (no wildcards)
allowed_methods [GET, POST, PUT, DELETE, OPTIONS] Allowed HTTP methods
allowed_headers [Accept, Authorization, Content-Type, X-Requested-With] Headers the client can send
exposed_headers [] Headers exposed to the browser
allow_credentials true Allow cookies and auth headers
max_age 86400 Preflight response cache time (seconds)

CORS Behavior

Example Scenarios

# Scenario 1: Same-origin only (default - most secure)
admin_portal:
  cors:
    allowed_origins: []  # Empty = same-origin only

# Scenario 2: Allow specific dashboard domain
admin_portal:
  cors:
    allowed_origins:
      - "https://admin.mycompany.com"

# Scenario 3: Development + Production
admin_portal:
  cors:
    allowed_origins:
      - "https://admin.mycompany.com"     # Production
      - "https://staging.mycompany.com"   # Staging
      - "http://localhost:3000"           # Local dev

Best Practice: Keep the allowed_origins list as small as possible. Remove development origins before deploying to production.

Creating Custom Rules

WafWay supports custom rules with full CRUD operations and database persistence:

# Create a custom rule via API
curl -X POST http://localhost:8080/api/v1/rules \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Block Admin Access",
    "description": "Block direct access to admin paths",
    "pattern": "^/admin",
    "action": "block",
    "target": "uri",
    "enabled": true,
    "priority": 100
  }'

Rule Patterns

Custom rules support regular expressions for pattern matching:

Target Description
uri Match against the request URI
body Match against the request body
headers Match against request headers
all Match against URI, body, and headers

Actions

Rules API

Method Endpoint Description
GET /api/v1/rules List all custom rules
POST /api/v1/rules Create a new rule
PUT /api/v1/rules/{id} Update an existing rule
DELETE /api/v1/rules/{id} Delete a rule
POST /api/v1/rules/{id}/toggle Toggle rule enabled/disabled

Traffic Data

WafWay collects and aggregates traffic data for analytics. Access via the REST API:

# Get traffic data for the last 24 hours
curl http://localhost:8080/api/v1/traffic \
  -H "Authorization: Bearer YOUR_TOKEN"

# Response
[
  {
    "timestamp": "2026-01-02T12:00:00Z",
    "total_requests": 1250,
    "blocked_count": 45,
    "allowed_count": 1205,
    "bytes_in": 524288,
    "bytes_out": 2097152,
    "avg_latency_ms": 12.5
  }
]

Top Paths

# Get most accessed paths
curl "http://localhost:8080/api/v1/traffic/top-paths?limit=10" \
  -H "Authorization: Bearer YOUR_TOKEN"

# Response
[
  {
    "path": "/api/users",
    "request_count": 5420,
    "blocked_count": 12,
    "avg_latency_ms": 8.3
  }
]

Attack Logs

All blocked requests are logged with full details:

# Get recent attacks
curl "http://localhost:8080/api/v1/traffic/attacks?limit=50" \
  -H "Authorization: Bearer YOUR_TOKEN"

# Response
[
  {
    "id": "1735812345-123456789",
    "timestamp": "2026-01-02T14:25:45Z",
    "source_ip": "192.168.1.100",
    "method": "GET",
    "path": "/api/users?id=1' OR '1'='1",
    "attack_type": "sqli",
    "rule_name": "SQL Injection Detection",
    "severity": "high",
    "action": "blocked"
  }
]

Storage Configuration

WafWay uses SQLite for persistent storage of rules, attack logs, and traffic data:

storage:
  type: "sqlite"
  path: "/var/lib/wafway/wafway.db"

# Data retention settings (optional)
# Older data is automatically cleaned up
retention:
  request_logs: 7d    # Keep request logs for 7 days
  attack_logs: 30d    # Keep attack logs for 30 days
  traffic_stats: 90d  # Keep aggregated stats for 90 days

Enterprise Feature: PostgreSQL and MySQL storage backends are available in the Enterprise edition for high-availability deployments.

API Overview

WafWay provides a REST API for programmatic access to all features.

Base URL: http://your-server:8080/api/v1

Authentication

All API requests require a Bearer token:

# Login to get token
curl -X POST http://localhost:8080/api/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username": "admin", "password": "your-password"}'

# Response
{
  "token": "xxx-your-token-xxx",
  "expires_at": "2026-01-03T00:00:00Z"
}

# Use token in requests
curl http://localhost:8080/api/v1/stats \
  -H "Authorization: Bearer xxx-your-token-xxx"

Endpoints

Method Endpoint Description
GET /api/v1/stats Get current statistics
GET /api/v1/health Health check status
GET /api/v1/ips/whitelist List whitelisted IPs
POST /api/v1/ips/whitelist Add IP to whitelist
GET /api/v1/rules List all rules
POST /api/v1/rules Create new rule
GET /api/v1/logs Get request logs
GET /api/v1/config Get configuration

Cloud Deployment

WafWay is a single compiled binary (~30MB) with zero runtime dependencies. It deploys on any Linux environment — cloud VMs, containers, or bare metal.

Supported Platforms

CloudRecommended VMArchitecture
AWSEC2 (t3.medium or Graviton c6g.medium)amd64 / arm64
GCPCompute Engine (e2-medium)amd64
AzureVirtual Machine (Standard_B2s)amd64
DigitalOceanDroplet (2 vCPU / 4GB)amd64
Any Linux2+ vCPU, 4GB+ RAMamd64 / arm64

Deployment Architecture

# Pattern A: Behind Load Balancer (Recommended)
Internet → Cloud LB (SSL termination, port 443)
               → WafWay VM (port 8081, HTTP inspection)
                    → App Server(s) (private subnet)

# Pattern B: Sidecar (Single VM)
Internet → Nginx (SSL, port 443)
               → WafWay (port 8081, same VM)
                    → App (localhost:3000)

# Pattern C: High Availability
Internet → Cloud LB
               → WafWay-1 ↔ Redis (shared state)
               → WafWay-2 ↔ Redis
                    → App Pool (N servers)

VM Installation

# Upload binary to VM
scp wafway user@vm-ip:~/

# Install
ssh user@vm-ip
sudo cp wafway /usr/local/bin/wafway
sudo chmod +x /usr/local/bin/wafway
sudo mkdir -p /etc/wafway /var/lib/wafway

# Configure
sudo vi /etc/wafway/wafway.yaml

# Start
sudo systemctl enable wafway
sudo systemctl start wafway

Security: Port 9090 (admin portal) must never be exposed to the public internet. Restrict via security groups to VPN or admin IPs only.

Kubernetes Deployment

Deploy WafWay as a Kubernetes Deployment or DaemonSet. The WAF runs as a pod and receives traffic from a Kubernetes Service or Ingress controller.

Architecture on Kubernetes

# Option A: WafWay as a Deployment behind a Service
Ingress Controller (nginx/traefik)
    → WafWay Service (ClusterIP, port 8081)
        → WafWay Pods (2+ replicas)
            → App Service (ClusterIP)
                → App Pods

# Option B: WafWay as a Sidecar in App Pod
Ingress Controller
    → App Pod
        |- wafway container (port 8081)
        |      → localhost:3000
        |- app container (port 3000)

Deployment Manifest

# wafway-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: wafway
  labels:
    app: wafway
spec:
  replicas: 2
  selector:
    matchLabels:
      app: wafway
  template:
    metadata:
      labels:
        app: wafway
    spec:
      containers:
        - name: wafway
          image: wafway-enterprise:1.2.0
          ports:
            - containerPort: 8081
              name: waf
            - containerPort: 9090
              name: admin
          volumeMounts:
            - name: config
              mountPath: /etc/wafway
            - name: data
              mountPath: /var/lib/wafway
          resources:
            requests:
              cpu: "500m"
              memory: "512Mi"
            limits:
              cpu: "2000m"
              memory: "2Gi"
          livenessProbe:
            httpGet:
              path: /api/health
              port: 9090
            initialDelaySeconds: 5
            periodSeconds: 10
          readinessProbe:
            httpGet:
              path: /api/health
              port: 9090
            initialDelaySeconds: 3
            periodSeconds: 5
      volumes:
        - name: config
          configMap:
            name: wafway-config
        - name: data
          persistentVolumeClaim:
            claimName: wafway-data

Service

# wafway-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: wafway
spec:
  selector:
    app: wafway
  ports:
    - name: waf
      port: 8081
      targetPort: 8081
    - name: admin
      port: 9090
      targetPort: 9090
  type: ClusterIP

ConfigMap

# wafway-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: wafway-config
data:
  wafway.yaml: |
    edition: enterprise
    server:
      listen_addr: "0.0.0.0"
      http_port: 8081
    backend:
      url: "http://app-service:3000"
      timeout: 30s
    protection:
      mode: "medium"
      sqli: { enabled: true }
      xss: { enabled: true }
      bot: { enabled: true, verify_good_bots: true }
      request_smuggling: { enabled: true, reject_ambiguous: true }
      dlp: { enabled: true, scan_credit_cards: true, action: "mask" }
    storage:
      type: sqlite
      path: /var/lib/wafway/wafway.db
    admin_portal:
      enabled: true
      listen: ":9090"
      jwt_secret: "change-me-to-a-secure-random-string"
      default_admin:
        username: admin
        password: "ChangeOnFirstLogin!2026"
    block_page:
      company_name: "Your Company"
    websocket:
      enabled: true

PersistentVolumeClaim (for SQLite)

# wafway-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: wafway-data
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 5Gi

Apply Everything

kubectl apply -f wafway-configmap.yaml
kubectl apply -f wafway-pvc.yaml
kubectl apply -f wafway-deployment.yaml
kubectl apply -f wafway-service.yaml

# Verify
kubectl get pods -l app=wafway
kubectl logs -l app=wafway --tail=20

Route Traffic Through WafWay

Update your Ingress to point to the WafWay service instead of your app service directly:

# ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: app-ingress
  annotations:
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
  tls:
    - hosts:
        - app.example.com
      secretName: app-tls
  rules:
    - host: app.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: wafway       # Points to WafWay, not app directly
                port:
                  number: 8081

Helm Chart

For production Kubernetes deployments, use the Helm chart for easier management:

# Install via Helm
helm install wafway ./charts/wafway \
  --set replicas=2 \
  --set config.backend.url="http://app-service:3000" \
  --set config.adminPortal.jwtSecret="your-secret-here" \
  --set persistence.enabled=true \
  --set persistence.size=5Gi

# Upgrade after config change
helm upgrade wafway ./charts/wafway -f values-production.yaml

# values-production.yaml
replicas: 3
resources:
  requests:
    cpu: "1000m"
    memory: "1Gi"
  limits:
    cpu: "4000m"
    memory: "4Gi"
config:
  backend:
    url: "http://app-service:3000"
  protection:
    mode: "medium"
    dlp:
      enabled: true
      scan_credit_cards: true
  adminPortal:
    jwtSecret: "production-secret-64-chars-minimum"
redis:
  enabled: true
  address: "redis-master:6379"

High Availability on Kubernetes

For production HA, run multiple WafWay replicas with shared state via Redis:

Architecture

Ingress Controller
    ↓
WafWay Service (ClusterIP)
    ↓ (load balanced across replicas)
WafWay Pod-1 ↔ Redis (shared rate limits, bans, sessions)
WafWay Pod-2 ↔ Redis
WafWay Pod-3 ↔ Redis
    ↓
App Service → App Pods

Redis Configuration

# Add to wafway.yaml ConfigMap for HA:
cluster:
  enabled: true
  node_id: "${HOSTNAME}"    # Auto-set per pod
  redis:
    enabled: true
    address: "redis-master.default.svc:6379"
    password: ""
    db: 0

# For shared database (instead of SQLite per-pod):
storage:
  type: postgres
  host: "postgres.default.svc"
  port: 5432
  database: wafway
  username: wafway
  password: "secure-password"
  ssl_mode: require

Pod Disruption Budget

# wafway-pdb.yaml
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: wafway-pdb
spec:
  minAvailable: 1
  selector:
    matchLabels:
      app: wafway

Horizontal Pod Autoscaler

# wafway-hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: wafway-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: wafway
  minReplicas: 2
  maxReplicas: 10
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70

Important: When running multiple replicas with SQLite, each pod has its own database. Virtual patches and routes created via admin portal will only exist on the pod that received the request. Use PostgreSQL + Redis for true multi-replica HA.

Docker Compose

For simpler deployments without Kubernetes, use Docker Compose:

# docker-compose.yml
version: '3.8'

services:
  wafway:
    image: wafway-enterprise:1.2.0
    ports:
      - "8081:8081"
      - "9090:9090"
    volumes:
      - ./wafway.yaml:/etc/wafway/wafway.yaml
      - wafway-data:/var/lib/wafway
    restart: always
    ulimits:
      nofile: 65535
    healthcheck:
      test: ["CMD", "wget", "-q", "--spider", "http://localhost:9090/api/health"]
      interval: 10s
      timeout: 5s
      retries: 3

volumes:
  wafway-data:

Docker Compose with Redis (HA)

# docker-compose-ha.yml
version: '3.8'

services:
  wafway-1:
    image: wafway-enterprise:1.2.0
    ports:
      - "8081:8081"
      - "9090:9090"
    volumes:
      - ./wafway-ha.yaml:/etc/wafway/wafway.yaml
    depends_on:
      - redis
    restart: always

  wafway-2:
    image: wafway-enterprise:1.2.0
    ports:
      - "8082:8081"
    volumes:
      - ./wafway-ha.yaml:/etc/wafway/wafway.yaml
    depends_on:
      - redis
    restart: always

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - redis-data:/data
    restart: always

volumes:
  redis-data:

Building the Docker Image

# Dockerfile (binary-only, no source code)
FROM alpine:3.19
RUN apk add --no-cache ca-certificates tzdata
COPY wafway /usr/local/bin/wafway
RUN chmod +x /usr/local/bin/wafway
RUN mkdir -p /etc/wafway /var/lib/wafway
EXPOSE 8081 9090
ENTRYPOINT ["/usr/local/bin/wafway", "-config", "/etc/wafway/wafway.yaml"]

# Build
docker build -t wafway-enterprise:1.2.0 .

# Save for distribution (no registry needed)
docker save wafway-enterprise:1.2.0 | gzip > wafway-docker-1.2.0.tar.gz

Need help? Contact us at support@wafway.com for deployment assistance or custom integration requirements.