events {
worker_connections 1024;
}
http {
# Upstream servers
upstream mcp_server {
server mcp-server:3000;
keepalive 32;
}
upstream mcp_hosting {
server mcp-hosting:3001;
keepalive 32;
}
# Rate limiting zones
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=100r/m;
limit_req_zone $binary_remote_addr zone=hosting_limit:10m rate=200r/m;
limit_conn_zone $binary_remote_addr zone=conn_limit:10m;
# 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 compression
gzip on;
gzip_types application/json text/plain text/css application/javascript text/event-stream;
gzip_min_length 1000;
# Map for determining if request is for hosted subdomain
map $host $is_hosted_subdomain {
default 0;
~^(?<subdomain>[^.]+)\.agenti\.cash$ 1;
}
map $host $extracted_subdomain {
default "";
~^(?<subdomain>[^.]+)\.agenti\.cash$ $subdomain;
}
# Main server - handles api.agenti.cash, www.agenti.cash, agenti.cash
server {
listen 80;
server_name agenti.cash www.agenti.cash api.agenti.cash;
# Health check endpoint (no rate limiting)
location /health {
proxy_pass http://mcp_server/health;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# MCP endpoint with SSE support
location /mcp {
limit_req zone=api_limit burst=20 nodelay;
limit_conn conn_limit 10;
proxy_pass http://mcp_server;
proxy_http_version 1.1;
proxy_set_header Connection '';
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;
# SSE specific settings
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
add_header X-Accel-Buffering no;
}
# Main API endpoint with rate limiting
location / {
limit_req zone=api_limit burst=20 nodelay;
limit_conn conn_limit 10;
proxy_pass http://mcp_server;
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;
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
# Error pages
error_page 429 /429.json;
location = /429.json {
default_type application/json;
return 429 '{"error": "Rate limit exceeded", "retry_after": 60}';
}
error_page 502 503 504 /50x.json;
location = /50x.json {
default_type application/json;
return 503 '{"error": "Service temporarily unavailable"}';
}
}
# Wildcard server - handles *.agenti.cash (hosted MCP servers)
server {
listen 80;
server_name *.agenti.cash;
# Skip reserved subdomains (handled by main server)
if ($extracted_subdomain ~* ^(www|api|app|admin|dashboard|docs)$) {
return 301 https://agenti.cash$request_uri;
}
# Health check for hosted servers
location /health {
proxy_pass http://mcp_hosting/health;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Subdomain $extracted_subdomain;
}
# MCP endpoint for hosted servers (SSE support)
location /mcp {
limit_req zone=hosting_limit burst=50 nodelay;
limit_conn conn_limit 20;
proxy_pass http://mcp_hosting;
proxy_http_version 1.1;
proxy_set_header Connection '';
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_set_header X-Subdomain $extracted_subdomain;
# SSE specific settings
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
add_header X-Accel-Buffering no;
}
# All other requests to hosted servers
location / {
limit_req zone=hosting_limit burst=50 nodelay;
limit_conn conn_limit 20;
proxy_pass http://mcp_hosting;
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;
proxy_set_header X-Subdomain $extracted_subdomain;
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
# Payment required response
error_page 402 /402.json;
location = /402.json {
default_type application/json;
return 402 '{"error": "Payment required", "code": 402, "x402": true}';
}
# Error pages
error_page 429 /429.json;
location = /429.json {
default_type application/json;
return 429 '{"error": "Rate limit exceeded", "retry_after": 60}';
}
error_page 502 503 504 /50x.json;
location = /50x.json {
default_type application/json;
return 503 '{"error": "Service temporarily unavailable"}';
}
}
# HTTPS server for main domain (uncomment when SSL certs are available)
# server {
# listen 443 ssl http2;
# server_name agenti.cash www.agenti.cash api.agenti.cash;
#
# ssl_certificate /etc/nginx/ssl/agenti.cash.pem;
# ssl_certificate_key /etc/nginx/ssl/agenti.cash.key;
# ssl_protocols TLSv1.2 TLSv1.3;
# ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
# ssl_prefer_server_ciphers off;
#
# # Same location blocks as HTTP server above...
# }
# HTTPS wildcard server (uncomment when wildcard SSL certs are available)
# server {
# listen 443 ssl http2;
# server_name *.agenti.cash;
#
# ssl_certificate /etc/nginx/ssl/wildcard.agenti.cash.pem;
# ssl_certificate_key /etc/nginx/ssl/wildcard.agenti.cash.key;
# ssl_protocols TLSv1.2 TLSv1.3;
# ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
# ssl_prefer_server_ciphers off;
#
# # Same location blocks as HTTP wildcard server above...
# }
}