Skip to main content

x402 Payment Integration Guide

Overview

The x402 protocol enables crypto payment requirements for API endpoints using the Coinbase Developer Platform (CDP). This guide covers how to integrate x402 payments into your ElizaOS deployment.

What is x402?

x402 is a payment protocol that allows you to monetize your API endpoints by requiring crypto payments (USDC) for each request. It works seamlessly with both testnet (for development) and mainnet (for production).

Key Benefits

  • Monetize APIs: Charge per API call in USDC
  • Blockchain-native: Payments are verified on-chain
  • Easy integration: Simple environment variable configuration
  • Flexible pricing: Set different prices per endpoint
  • Testnet support: Test with Base Sepolia before going live

Quick Start

1. Get Your Wallet Address

You need an EVM-compatible wallet address to receive payments. This should be a Base or Base Sepolia address. Important: Your wallet address must:
  • Start with 0x
  • Be exactly 42 characters long
  • Contain only hexadecimal characters (0-9, a-f, A-F)
Example: 0x1234567890abcdef1234567890abcdef12345678

2. Configure Environment Variables

Add these variables to your .env file:

Required (when enabling x402)

X402_ENABLED=true
X402_WALLET_ADDRESS=0xYourWalletAddressHere

Optional Configuration

# Price per API call (default: $0.01)
X402_PRICE=$0.001

# Network: "base-sepolia" for testnet, "base" for mainnet (default: base-sepolia)
X402_NETWORK=base-sepolia

# Facilitator URL for testnet (default: https://x402.org/facilitator)
X402_FACILITATOR_URL=https://x402.org/facilitator

# Use mainnet facilitator (requires CDP credentials)
X402_USE_MAINNET=false
Start with Base Sepolia testnet to test your integration:
X402_ENABLED=true
X402_WALLET_ADDRESS=0xYourSepoliaWalletAddress
X402_NETWORK=base-sepolia
X402_PRICE=$0.001
Get Testnet USDC: Use the Base Sepolia faucet to get test tokens.

4. Mainnet Setup

For production, you’ll need CDP API credentials:

Get CDP Credentials

  1. Sign up at Coinbase Developer Platform
  2. Create an API key
  3. Save your API Key ID and Secret

Configure Mainnet

X402_ENABLED=true
X402_USE_MAINNET=true
X402_WALLET_ADDRESS=0xYourMainnetWalletAddress
X402_NETWORK=base
X402_PRICE=$0.01

# CDP Credentials (required for mainnet)
CDP_API_KEY_ID=your-api-key-id
CDP_API_KEY_SECRET=your-api-key-secret

Authentication Modes

The x402 middleware supports multiple authentication configurations:

Mode 1: x402 Only

Users pay with crypto, no API key required.
X402_ENABLED=true
X402_WALLET_ADDRESS=0xYourAddress
# ELIZA_SERVER_AUTH_TOKEN not set
Use case: Public monetized API

Mode 2: API Key Only

Traditional API key authentication, no payment required.
X402_ENABLED=false
ELIZA_SERVER_AUTH_TOKEN=your-secret-api-key
Use case: Private API for known clients

Mode 3: Both x402 + API Key

Requires BOTH valid API key AND payment.
X402_ENABLED=true
X402_WALLET_ADDRESS=0xYourAddress
ELIZA_SERVER_AUTH_TOKEN=your-secret-api-key
Use case: Restricted monetized API

Mode 4: Open Access

No authentication required (not recommended for production).
X402_ENABLED=false
# ELIZA_SERVER_AUTH_TOKEN not set
Use case: Development/testing only

Jobs API Integration

The Jobs API (POST /api/messaging/jobs) and status endpoint (GET /api/messaging/jobs/:jobId) both use x402 middleware for consistent authentication.

Per-Endpoint Configuration

You can customize pricing and metadata per endpoint:
# Jobs endpoint price (overrides X402_PRICE)
X402_JOBS_PRICE=$0.005

# Optional: Add API discovery metadata
X402_JOBS_ENDPOINT_DESCRIPTION="Submit a one-time message to an AI agent"

# Optional: Define input/output schemas for API documentation
X402_JOBS_INPUT_SCHEMA='{"type":"object","properties":{"agentId":{"type":"string"},"content":{"type":"string"}}}'
X402_JOBS_OUTPUT_SCHEMA='{"type":"object","properties":{"jobId":{"type":"string"},"status":{"type":"string"}}}'

Making Payments as a Client

When x402 is enabled, clients must include an X-PAYMENT header with payment proof.

Using x402 SDK (JavaScript/TypeScript)

import { Payment } from '@coinbase/x402';

// Create payment for endpoint
const payment = await Payment.create({
  endpoint: 'https://your-api.com/api/messaging/jobs',
  method: 'POST',
  network: 'base-sepolia',
  facilitatorUrl: 'https://x402.org/facilitator',
});

// Make request with payment header
const response = await fetch('https://your-api.com/api/messaging/jobs', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-PAYMENT': payment.proof,
    // Include API key if both auth methods are enabled
    'X-API-KEY': 'your-api-key',
  },
  body: JSON.stringify({
    agentId: 'agent-123',
    content: 'Hello, agent!',
  }),
});

Example: Complete Client Flow

import { Payment } from '@coinbase/x402';

async function callJobsAPI(agentId: string, content: string) {
  const apiUrl = 'https://your-api.com/api/messaging/jobs';

  // Step 1: Create payment
  const payment = await Payment.create({
    endpoint: apiUrl,
    method: 'POST',
    network: 'base-sepolia',
    facilitatorUrl: 'https://x402.org/facilitator',
  });

  // Step 2: Submit job
  const response = await fetch(apiUrl, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-PAYMENT': payment.proof,
    },
    body: JSON.stringify({ agentId, content }),
  });

  const job = await response.json();
  console.log('Job created:', job.id);

  // Step 3: Check job status (uses same payment method)
  const statusPayment = await Payment.create({
    endpoint: `${apiUrl}/${job.id}`,
    method: 'GET',
    network: 'base-sepolia',
    facilitatorUrl: 'https://x402.org/facilitator',
  });

  const statusResponse = await fetch(`${apiUrl}/${job.id}`, {
    headers: {
      'X-PAYMENT': statusPayment.proof,
    },
  });

  const status = await statusResponse.json();
  console.log('Job status:', status);

  return status;
}

Security Best Practices

1. Protect Your Wallet Private Keys

Never commit private keys to version control:
# .gitignore
.env
.env.local
.env.*.local

2. Use Different Wallets for Test/Prod

Keep separate wallets for testnet and mainnet:
# .env.testnet
X402_WALLET_ADDRESS=0xTestnetWalletAddress
X402_NETWORK=base-sepolia

# .env.production
X402_WALLET_ADDRESS=0xProductionWalletAddress
X402_NETWORK=base
X402_USE_MAINNET=true

3. Rotate CDP API Keys Regularly

Set up key rotation for your CDP credentials:
# Use secrets management
CDP_API_KEY_ID=$(vault read secret/cdp/key-id)
CDP_API_KEY_SECRET=$(vault read secret/cdp/key-secret)

4. Monitor Payment Flow

Track payments and detect anomalies:
// Set up monitoring
logger.info('[x402] Payment received', {
  wallet: config.walletAddress,
  network: config.network,
  amount: payment.amount,
});

Troubleshooting

Error: “Invalid or missing X-PAYMENT”

Cause: Payment header is missing or invalid Solution: Ensure client is sending valid payment proof in X-PAYMENT header

Error: “X402_WALLET_ADDRESS must start with 0x”

Cause: Wallet address format is invalid Solution: Verify your wallet address:
  • Starts with 0x
  • Is exactly 42 characters
  • Contains only hex characters (0-9, a-f, A-F)

Error: “Mainnet facilitator requires CDP_API_KEY_ID”

Cause: Mainnet is enabled but CDP credentials are missing Solution: Set both CDP_API_KEY_ID and CDP_API_KEY_SECRET when using mainnet

Payment Not Received

Check:
  1. Wallet address is correct
  2. Network matches (testnet vs mainnet)
  3. Client is using correct facilitator URL
  4. USDC is available in client wallet

Testing

Unit Tests

Test your x402 configuration:
import { describe, test, expect } from 'bun:test';
import { createX402Middleware } from '../middleware/x402';

describe('x402 Configuration', () => {
  test('should validate wallet address format', () => {
    process.env.X402_ENABLED = 'true';
    process.env.X402_WALLET_ADDRESS = 'invalid-address';

    expect(() => createX402Middleware({})).toThrow('X402_WALLET_ADDRESS must start with 0x');
  });
});

Integration Tests

Test the complete payment flow:
# Run with testnet configuration
elizaos test --name "x402 payment flow"

Production Deployment with Nginx

Why Use Nginx?

Nginx acts as a reverse proxy to provide:
  • SSL/TLS termination: Secure HTTPS connections
  • Rate limiting: Prevent abuse and DDoS attacks
  • Load balancing: Distribute traffic across multiple instances
  • Static file serving: Offload static content from your API
  • Security headers: Add security layers
  • Access control: Restrict access by IP, geography, etc.

Step 1: Install Nginx

Ubuntu/Debian

sudo apt update
sudo apt install nginx
sudo systemctl start nginx
sudo systemctl enable nginx

CentOS/RHEL

sudo yum install epel-release
sudo yum install nginx
sudo systemctl start nginx
sudo systemctl enable nginx

macOS

brew install nginx
brew services start nginx

Step 2: Basic Nginx Configuration

Create a new configuration file for your ElizaOS API:
sudo nano /etc/nginx/sites-available/eliza-api
Add the following configuration:
# ElizaOS API - Basic Configuration
upstream eliza_backend {
    # Your ElizaOS server (default port 3000)
    server 127.0.0.1:3000;

    # For multiple instances (load balancing):
    # server 127.0.0.1:3001;
    # server 127.0.0.1:3002;
}

server {
    listen 80;
    server_name api.yourdomain.com;

    # Redirect all HTTP to HTTPS
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name api.yourdomain.com;

    # SSL Configuration (we'll add certificates next)
    ssl_certificate /etc/letsencrypt/live/api.yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/api.yourdomain.com/privkey.pem;

    # Security Headers
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Referrer-Policy "no-referrer-when-downgrade" always;
    add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always;
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

    # Logging
    access_log /var/log/nginx/eliza-api-access.log;
    error_log /var/log/nginx/eliza-api-error.log;

    # Client body size limit (for file uploads)
    client_max_body_size 10M;

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

    # Proxy to ElizaOS backend
    location / {
        proxy_pass http://eliza_backend;
        proxy_http_version 1.1;

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

        # Forward headers
        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;

        # Preserve original request headers
        proxy_pass_request_headers on;
    }

    # Health check endpoint (optional)
    location /health {
        access_log off;
        proxy_pass http://eliza_backend/api/health;
    }
}
Enable the configuration:
sudo ln -s /etc/nginx/sites-available/eliza-api /etc/nginx/sites-enabled/
sudo nginx -t  # Test configuration
sudo systemctl reload nginx

Step 3: SSL/TLS with Let’s Encrypt

Install Certbot

# Ubuntu/Debian
sudo apt install certbot python3-certbot-nginx

# CentOS/RHEL
sudo yum install certbot python3-certbot-nginx

Obtain SSL Certificate

sudo certbot --nginx -d api.yourdomain.com
Follow the prompts to:
  1. Enter your email
  2. Agree to Terms of Service
  3. Choose whether to redirect HTTP to HTTPS (recommended: yes)

Auto-renewal

Certbot automatically sets up renewal. Test it:
sudo certbot renew --dry-run

Step 4: Rate Limiting

Protect your API from abuse with rate limiting:
# Add at the top of /etc/nginx/sites-available/eliza-api

# Define rate limit zones
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=jobs_limit:10m rate=5r/s;
limit_req_zone $binary_remote_addr zone=strict_limit:10m rate=1r/s;

# Connection limit
limit_conn_zone $binary_remote_addr zone=conn_limit:10m;

server {
    # ... existing configuration ...

    # General API rate limit (10 requests/second)
    location /api/ {
        limit_req zone=api_limit burst=20 nodelay;
        limit_conn conn_limit 10;

        proxy_pass http://eliza_backend;
        # ... other proxy settings ...
    }

    # Jobs API - stricter limit (5 requests/second)
    location /api/messaging/jobs {
        limit_req zone=jobs_limit burst=10 nodelay;
        limit_conn conn_limit 5;

        proxy_pass http://eliza_backend;
        # ... other proxy settings ...
    }

    # Admin endpoints - very strict (1 request/second)
    location /api/admin/ {
        limit_req zone=strict_limit burst=2 nodelay;
        limit_conn conn_limit 1;

        # Optional: IP whitelist
        allow 203.0.113.0/24;  # Your office IP range
        deny all;

        proxy_pass http://eliza_backend;
        # ... other proxy settings ...
    }
}

Step 5: DDoS Protection

Enable Connection Limits

http {
    # Limit connections per IP
    limit_conn_zone $binary_remote_addr zone=addr:10m;

    # Limit requests per IP
    limit_req_zone $binary_remote_addr zone=one:10m rate=100r/s;

    server {
        # Apply limits
        limit_conn addr 10;
        limit_req zone=one burst=200 nodelay;

        # Timeout configurations
        client_body_timeout 10s;
        client_header_timeout 10s;
        keepalive_timeout 30s;
        send_timeout 10s;
    }
}

Install Fail2ban

Automatically ban IPs with suspicious behavior:
sudo apt install fail2ban
Create Nginx jail configuration:
sudo nano /etc/fail2ban/jail.d/nginx.conf
[nginx-req-limit]
enabled = true
filter = nginx-req-limit
action = iptables-multiport[name=ReqLimit, port="http,https", protocol=tcp]
logpath = /var/log/nginx/*error.log
findtime = 600
bantime = 7200
maxretry = 10

[nginx-http-auth]
enabled = true
filter = nginx-http-auth
action = iptables-multiport[name=NoAuthFailures, port="http,https", protocol=tcp]
logpath = /var/log/nginx/*error.log
findtime = 600
bantime = 7200
maxretry = 5
Create filter:
sudo nano /etc/fail2ban/filter.d/nginx-req-limit.conf
[Definition]
failregex = limiting requests, excess:.* by zone.*client: <HOST>
ignoreregex =
Start Fail2ban:
sudo systemctl enable fail2ban
sudo systemctl start fail2ban

Step 6: Geographic Restrictions (Optional)

Restrict access by country using GeoIP:
# Install GeoIP module
sudo apt install libnginx-mod-http-geoip2 geoipupdate
Configure GeoIP:
http {
    # Load GeoIP database
    geoip2 /usr/share/GeoIP/GeoLite2-Country.mmdb {
        $geoip2_country_code country iso_code;
    }

    # Define allowed countries
    map $geoip2_country_code $allowed_country {
        default no;
        US yes;
        CA yes;
        GB yes;
        # Add your allowed countries
    }

    server {
        location /api/ {
            if ($allowed_country = no) {
                return 403 "Access denied from your country";
            }
            # ... proxy settings ...
        }
    }
}

Step 7: Monitoring and Logging

Enhanced Logging Format

http {
    # Custom log format with more details
    log_format detailed '$remote_addr - $remote_user [$time_local] '
                       '"$request" $status $body_bytes_sent '
                       '"$http_referer" "$http_user_agent" '
                       '"$http_x_forwarded_for" '
                       'rt=$request_time uct="$upstream_connect_time" '
                       'uht="$upstream_header_time" urt="$upstream_response_time"';

    access_log /var/log/nginx/eliza-api-access.log detailed;
}

Log Rotation

sudo nano /etc/logrotate.d/nginx
/var/log/nginx/*.log {
    daily
    missingok
    rotate 14
    compress
    delaycompress
    notifempty
    create 0640 www-data adm
    sharedscripts
    postrotate
        if [ -f /var/run/nginx.pid ]; then
            kill -USR1 `cat /var/run/nginx.pid`
        fi
    endscript
}

Monitor with GoAccess (Real-time)

sudo apt install goaccess

# Real-time monitoring
sudo goaccess /var/log/nginx/eliza-api-access.log -o /var/www/html/report.html --log-format=COMBINED --real-time-html
Access at: https://api.yourdomain.com/report.html

Step 8: Advanced Security

IP Whitelist for Admin Endpoints

# Allow only specific IPs for admin routes
location /api/admin/ {
    # Office network
    allow 203.0.113.0/24;

    # VPN exit IP
    allow 198.51.100.42;

    # Block everyone else
    deny all;

    proxy_pass http://eliza_backend;
}

Hide Nginx Version

http {
    server_tokens off;
}

Add WAF (Web Application Firewall)

Install ModSecurity:
sudo apt install libnginx-mod-http-modsecurity

# Enable ModSecurity
sudo nano /etc/nginx/modsecurity/modsecurity.conf
Change SecRuleEngine DetectionOnly to SecRuleEngine On

Request Body Validation

location /api/messaging/jobs {
    # Only allow POST
    limit_except POST {
        deny all;
    }

    # Require Content-Type header
    if ($content_type !~ "application/json") {
        return 415;
    }

    proxy_pass http://eliza_backend;
}

Step 9: High Availability Setup

Multiple ElizaOS Instances

upstream eliza_backend {
    least_conn;  # Load balancing method

    # Multiple instances
    server 127.0.0.1:3000 max_fails=3 fail_timeout=30s;
    server 127.0.0.1:3001 max_fails=3 fail_timeout=30s;
    server 127.0.0.1:3002 max_fails=3 fail_timeout=30s;

    # Health check
    keepalive 32;
}

Run Multiple Instances with PM2

# Install PM2
npm install -g pm2

# Create ecosystem file
cat > ecosystem.config.js << 'EOF'
module.exports = {
  apps: [
    {
      name: 'eliza-api-1',
      script: 'elizaos',
      args: 'start',
      env: {
        PORT: 3000,
        NODE_ENV: 'production'
      }
    },
    {
      name: 'eliza-api-2',
      script: 'elizaos',
      args: 'start',
      env: {
        PORT: 3001,
        NODE_ENV: 'production'
      }
    },
    {
      name: 'eliza-api-3',
      script: 'elizaos',
      args: 'start',
      env: {
        PORT: 3002,
        NODE_ENV: 'production'
      }
    }
  ]
};
EOF

# Start all instances
pm2 start ecosystem.config.js
pm2 save
pm2 startup

Step 10: Production Deployment Checklist

Before going live, verify:

Security Checklist

  • SSL/TLS certificate installed and valid
  • HTTP to HTTPS redirect enabled
  • Security headers configured
  • Rate limiting enabled on all endpoints
  • Fail2ban configured and running
  • Admin endpoints IP-whitelisted
  • Nginx version hidden (server_tokens off)
  • WAF/ModSecurity enabled (optional but recommended)
  • Log rotation configured
  • Firewall rules set (only ports 80, 443 open)

x402 Configuration Checklist

  • X402_ENABLED=true for production
  • X402_WALLET_ADDRESS set to mainnet address
  • Wallet address validated (starts with 0x, 42 chars, hex only)
  • X402_USE_MAINNET=true set
  • CDP_API_KEY_ID and CDP_API_KEY_SECRET configured
  • X402_NETWORK=base (not base-sepolia)
  • Pricing configured appropriately
  • Endpoint metadata added for API discovery

Environment Checklist

  • .env file secured (not committed to git)
  • Secrets stored in vault/secrets manager
  • Database backups configured
  • Monitoring and alerting set up
  • Error tracking configured (Sentry, etc.)
  • Log aggregation set up (ELK, Datadog, etc.)

Testing Checklist

  • Load testing completed
  • DDoS protection tested
  • SSL certificate verified (SSL Labs test)
  • API endpoints tested with x402 payments
  • Failover tested (if using multiple instances)
  • Backup and restore tested

Complete Production Nginx Configuration

Here’s a complete, production-ready configuration:
# /etc/nginx/nginx.conf - Global settings
user www-data;
worker_processes auto;
pid /run/nginx.pid;

events {
    worker_connections 4096;
    use epoll;
    multi_accept on;
}

http {
    # Basic Settings
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 30;
    types_hash_max_size 2048;
    server_tokens off;

    # MIME
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    # SSL Settings
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers on;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;
    ssl_stapling on;
    ssl_stapling_verify on;

    # Logging
    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                    '$status $body_bytes_sent "$http_referer" '
                    '"$http_user_agent" "$http_x_forwarded_for" '
                    'rt=$request_time uct="$upstream_connect_time" '
                    'uht="$upstream_header_time" urt="$upstream_response_time"';

    access_log /var/log/nginx/access.log main;
    error_log /var/log/nginx/error.log warn;

    # Gzip Settings
    gzip on;
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 6;
    gzip_types text/plain text/css text/xml text/javascript
               application/json application/javascript application/xml+rss
               application/rss+xml application/atom+xml image/svg+xml
               text/x-component application/x-font-ttf font/opentype
               application/vnd.ms-fontobject;

    # Rate Limiting
    limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
    limit_req_zone $binary_remote_addr zone=jobs:10m rate=5r/s;
    limit_req_zone $binary_remote_addr zone=strict:10m rate=1r/s;
    limit_conn_zone $binary_remote_addr zone=conn:10m;

    # Include site configurations
    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
}
# /etc/nginx/sites-available/eliza-api - Site configuration
upstream eliza_backend {
    least_conn;
    server 127.0.0.1:3000 max_fails=3 fail_timeout=30s;
    server 127.0.0.1:3001 max_fails=3 fail_timeout=30s backup;
    keepalive 32;
}

# HTTP - Redirect to HTTPS
server {
    listen 80;
    listen [::]:80;
    server_name api.yourdomain.com;

    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }

    location / {
        return 301 https://$server_name$request_uri;
    }
}

# HTTPS - Main Configuration
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name api.yourdomain.com;

    # SSL Certificates
    ssl_certificate /etc/letsencrypt/live/api.yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/api.yourdomain.com/privkey.pem;
    ssl_trusted_certificate /etc/letsencrypt/live/api.yourdomain.com/chain.pem;

    # Security Headers
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;

    # Logging
    access_log /var/log/nginx/eliza-api-access.log main buffer=32k flush=1m;
    error_log /var/log/nginx/eliza-api-error.log warn;

    # Client settings
    client_max_body_size 10M;
    client_body_buffer_size 128k;
    client_header_buffer_size 1k;
    large_client_header_buffers 4 16k;

    # Timeouts
    client_body_timeout 12s;
    client_header_timeout 12s;
    proxy_connect_timeout 60s;
    proxy_send_timeout 60s;
    proxy_read_timeout 60s;
    send_timeout 10s;

    # Health check (no rate limit, no logging)
    location /health {
        access_log off;
        limit_req off;
        proxy_pass http://eliza_backend/api/health;
    }

    # Jobs API - x402 protected
    location /api/messaging/jobs {
        limit_req zone=jobs burst=10 nodelay;
        limit_conn conn 5;

        # Only allow POST and GET
        limit_except GET POST {
            deny all;
        }

        proxy_pass http://eliza_backend;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        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_pass_request_headers on;

        # CORS (if needed)
        add_header Access-Control-Allow-Origin "*" always;
        add_header Access-Control-Allow-Methods "GET, POST, OPTIONS" always;
        add_header Access-Control-Allow-Headers "Content-Type, X-API-KEY, X-PAYMENT" always;

        if ($request_method = OPTIONS) {
            return 204;
        }
    }

    # General API
    location /api/ {
        limit_req zone=api burst=20 nodelay;
        limit_conn conn 10;

        proxy_pass http://eliza_backend;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        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_pass_request_headers on;
    }

    # Admin API - IP restricted
    location /api/admin/ {
        limit_req zone=strict burst=2 nodelay;
        limit_conn conn 1;

        # Whitelist
        allow 203.0.113.0/24;  # Replace with your IP
        deny all;

        proxy_pass http://eliza_backend;
        proxy_http_version 1.1;
        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;
    }

    # Static files (if any)
    location /static/ {
        alias /var/www/eliza/static/;
        expires 1y;
        add_header Cache-Control "public, immutable";
    }

    # Deny access to hidden files
    location ~ /\. {
        deny all;
        access_log off;
        log_not_found off;
    }
}

Monitoring Production

Setup Prometheus + Grafana

Monitor Nginx metrics:
# Install nginx-prometheus-exporter
wget https://github.com/nginxinc/nginx-prometheus-exporter/releases/download/v0.11.0/nginx-prometheus-exporter_0.11.0_linux_amd64.tar.gz
tar -xzf nginx-prometheus-exporter_0.11.0_linux_amd64.tar.gz
sudo mv nginx-prometheus-exporter /usr/local/bin/

# Create systemd service
sudo nano /etc/systemd/system/nginx-exporter.service
[Unit]
Description=Nginx Prometheus Exporter
After=network.target

[Service]
Type=simple
User=www-data
ExecStart=/usr/local/bin/nginx-prometheus-exporter -nginx.scrape-uri=http://localhost:8080/stub_status
Restart=on-failure

[Install]
WantedBy=multi-user.target
sudo systemctl enable nginx-exporter
sudo systemctl start nginx-exporter

Alerting

Set up alerts for:
  • High error rates (> 5%)
  • Response time > 2s
  • Rate limit hits
  • SSL certificate expiry (< 30 days)
  • Disk space (> 80%)

Resources

Support

For issues and questions: