This guide explains how to configure reverse proxy servers for TRUF.NETWORK's MCP (Model Context Protocol) server when using SSE (Server-Sent Events) transport.
💡 Quick Start: Ready-to-use configuration files and Docker Compose setups are available in examples/mcp-reverse-proxy/.
The TRUF.NETWORK MCP server supports two transport modes:
- stdio: Direct process communication (default, used by Claude Desktop)
- sse: Server-Sent Events over HTTP (for remote connections and multi-client support)
When deploying the MCP server with SSE transport behind a reverse proxy, specific configuration is required to ensure proper streaming behavior.
SSE (Server-Sent Events) is a streaming technology that maintains persistent HTTP connections to deliver real-time data. Unlike regular HTTP requests, SSE requires:
- No buffering - Data must be streamed immediately, not buffered
- Persistent connections - Long-lived connections that don't timeout prematurely
- Proper headers - Specific HTTP headers for event streaming
- HTTP/1.1 support - Streaming requires HTTP/1.1 features
All reverse proxy configurations must include these essential settings:
Most Important: Buffering breaks real-time streaming
- Response data must pass through immediately
- No waiting for response completion
- Required for streaming connections
- Enables proper chunked transfer encoding
- Clear or properly manage Connection headers
- Prevent connection pooling interference
- SSE responses should never be cached
- Each event stream is unique and live
Create a server block with SSE-specific settings:
server {
listen 80;
server_name your-domain.com;
location /sse {
proxy_pass http://localhost:8000/sse;
# Essential SSE settings
proxy_set_header Connection '';
proxy_http_version 1.1;
proxy_buffering off;
proxy_cache off;
chunked_transfer_encoding off;
# Standard proxy 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;
}
}server {
listen 443 ssl http2;
server_name your-domain.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location /sse {
proxy_pass http://localhost:8000/sse;
# Essential SSE settings
proxy_set_header Connection '';
proxy_http_version 1.1;
proxy_buffering off;
proxy_cache off;
chunked_transfer_encoding off;
# Headers for HTTPS
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 https;
}
}Caddy has built-in SSE support with minimal configuration:
your-domain.com {
reverse_proxy /sse/* localhost:8000 {
# Caddy handles SSE automatically
# Optional: Add custom headers if needed
header_up Host {http.reverse_proxy.upstream.hostport}
}
}your-domain.com {
reverse_proxy /sse/* localhost:8000 {
# Disable buffering for SSE
flush_interval -1
# Custom headers
header_up Host {http.reverse_proxy.upstream.hostport}
header_up X-Real-IP {http.request.remote}
header_up X-Forwarded-Proto {http.request.scheme}
}
}version: '3.8'
services:
mcp-server:
image: trufnetwork/postgres-mcp:latest
command: ["--transport=sse", "--sse-host=0.0.0.0", "--access-mode=restricted"]
environment:
- DATABASE_URI=postgresql://kwild:password@postgres:5432/kwild
labels:
- "traefik.enable=true"
- "traefik.http.routers.mcp.rule=Host(`your-domain.com`) && PathPrefix(`/sse`)"
- "traefik.http.services.mcp.loadbalancer.server.port=8000"
# SSE-specific labels
- "traefik.http.middlewares.sse-headers.headers.customrequestheaders.Connection="
- "traefik.http.routers.mcp.middlewares=sse-headers"
traefik:
image: traefik:v3.0
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sockapi:
dashboard: true
entryPoints:
web:
address: ":80"
websecure:
address: ":443"
providers:
docker:
exposedByDefault: false
# Optional: Enable HTTPS with Let's Encrypt
certificatesResolvers:
letsencrypt:
acme:
email: admin@your-domain.com
storage: /letsencrypt/acme.json
httpChallenge:
entryPoint: webglobal
daemon
log stdout local0
defaults
mode http
timeout connect 5000ms
timeout client 60000ms
timeout server 60000ms
# Important for SSE
timeout client-fin 30s
timeout server-fin 30s
frontend mcp_frontend
bind *:80
acl is_sse path_beg /sse
use_backend mcp_sse if is_sse
backend mcp_sse
# SSE requires HTTP/1.1
http-request set-header Connection ""
http-request set-header Host %[req.hdr(host)]
# Disable buffering for streaming
no option httpchk
option http-server-close
server mcp1 localhost:8000 check
<VirtualHost *:80>
ServerName your-domain.com
<Location /sse>
ProxyPass http://localhost:8000/sse
ProxyPassReverse http://localhost:8000/sse
# Essential for SSE
ProxyPreserveHost On
ProxyRequests Off
# Disable buffering
SetEnv proxy-nokeepalive 1
SetEnv proxy-initial-not-pooled 1
# Headers
ProxyPassReverse /
ProxyPassReverseRewrite /
</Location>
</VirtualHost>
# Enable required modules
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so
LoadModule headers_module modules/mod_headers.soImportant: TRUF.NETWORK uses the Kwil PostgreSQL Docker image (ghcr.io/trufnetwork/kwil-postgres) which is configured with POSTGRES_HOST_AUTH_METHOD=trust and automatically creates a kwild database with a kwild user. This matches the standard node setup described in the Node Operator Guide.
docker run -p 8000:8000 \
-e DATABASE_URI=postgresql://kwild@localhost:5432/kwild \
trufnetwork/postgres-mcp \
--access-mode=restricted \
--transport=sse \
--sse-host=0.0.0.0 \
--sse-port=8000# Install the TRUF.NETWORK postgres-mcp
git clone https://github.com/trufnetwork/postgres-mcp.git
cd postgres-mcp
./install.sh
# Run with SSE transport
DATABASE_URI=postgresql://kwild@localhost:5432/kwild \
postgres-mcp --transport=sse --sse-host=0.0.0.0 --sse-port=8000 --access-mode=restrictedUpdate your MCP client configuration to use the SSE endpoint:
{
"mcpServers": {
"truf-postgres": {
"type": "sse",
"url": "http://your-domain.com/sse"
}
}
}{
"mcpServers": {
"truf-postgres": {
"type": "sse",
"url": "http://your-domain.com/sse"
}
}
}{
"mcpServers": {
"truf-postgres": {
"type": "sse",
"serverUrl": "http://your-domain.com/sse"
}
}
}mcp-remote proxy tool.
{
"mcpServers": {
"truf-postgres": {
"command": "npx",
"args": [
"mcp-remote",
"http://your-domain.com/sse"
]
}
}
}Configuration Location:
- Windows:
%APPDATA%\Claude\claude_desktop_config.json - macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Linux:
~/.config/Claude/claude_desktop_config.json
# Test basic connectivity
curl -v http://your-domain.com/sse
# Test with SSE headers
curl -H "Accept: text/event-stream" \
-H "Cache-Control: no-cache" \
http://your-domain.com/sseCheck that responses stream immediately without buffering:
# Should show immediate response headers
curl -v -N -H "Accept: text/event-stream" http://your-domain.com/sse- Configure your MCP client with the SSE URL
- Verify the client can connect and list tools
- Test executing queries through the SSE transport
# Check nginx access logs for SSE requests
tail -f /var/log/nginx/access.log | grep "/sse"
# Monitor MCP server logs
docker logs -f your-mcp-containerSymptoms: SSE connections drop after 30-60 seconds Solution: Increase proxy timeouts
# Nginx
location /sse {
proxy_read_timeout 300s;
proxy_send_timeout 300s;
# ... other settings
}# Caddy
reverse_proxy /sse/* localhost:8000 {
timeout 5m
}Symptoms: No data until connection closes Solution: Ensure buffering is disabled
# Nginx - Most critical setting
proxy_buffering off;
# Also verify chunked transfer encoding
chunked_transfer_encoding off;Symptoms: Browser-based clients can't connect Solution: Add CORS headers
# Nginx
location /sse {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Headers' 'Origin, X-Requested-With, Content-Type, Accept, Authorization';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
# Handle preflight requests
if ($request_method = 'OPTIONS') {
return 204;
}
# ... other proxy settings
}Symptoms: HTTPS connections fail Solution: Verify certificate configuration and headers
# Ensure proper forwarded headers for HTTPS
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-Port 443;Symptoms: MCP tools fail with database errors Solution: Verify DATABASE_URI and network connectivity
# Test database connectivity from MCP server (TRUF.NETWORK uses kwild user/database)
docker exec truf-mcp-server psql postgresql://kwild@localhost:5432/kwild -c "SELECT version();"
# Or connect to the TRUF.NETWORK postgres container directly
docker exec -it tn-postgres psql -U kwild -d kwild
### Issue: Windows IPv4/IPv6 Addressing Problems
**Symptoms**: Database connection timeouts on Windows, especially with SSH tunnels
**Solution**: Use explicit IPv4 addresses instead of localhost
```powershell
# Wrong: May resolve to IPv6 (::1) which breaks SSH tunnels
$env:DATABASE_URI = "postgresql://kwild@localhost:5432/kwild"
# Correct: Force IPv4 addressing
$env:DATABASE_URI = "postgresql://kwild@127.0.0.1:5432/kwild"
# Start MCP server with IPv4
postgres-mcp --transport=sse --sse-host=127.0.0.1 --sse-port=8000
# Test connectivity
Test-NetConnection -ComputerName localhost -Port 5432Symptoms: Claude Desktop shows "Required" errors for missing "command" field
Solution: Claude Desktop requires mcp-remote proxy for SSE connections
# Wrong: Direct SSE configuration not supported
{
"mcpServers": {
"truf-postgres": {
"type": "sse",
"url": "http://localhost:8000/sse"
}
}
}
# Correct: Use mcp-remote proxy
{
"mcpServers": {
"truf-postgres": {
"command": "npx",
"args": ["mcp-remote", "http://127.0.0.1:8000/sse"]
}
}
}# Restrict access by IP
location /sse {
allow 192.168.1.0/24;
allow 10.0.0.0/8;
deny all;
# ... proxy settings
}# Nginx rate limiting
http {
limit_req_zone $binary_remote_addr zone=sse:10m rate=10r/m;
server {
location /sse {
limit_req zone=sse burst=5;
# ... proxy settings
}
}
}Consider implementing authentication at the reverse proxy level:
# Basic auth example
location /sse {
auth_basic "MCP Server Access";
auth_basic_user_file /etc/nginx/.htpasswd;
# ... proxy settings
}- Use HTTPS in production
- Implement firewall rules
- Use VPNs or private networks when possible
- Regular security updates for proxy software
Here's a complete production-ready nginx configuration:
# /etc/nginx/sites-available/mcp-server
server {
listen 443 ssl http2;
server_name mcp.your-domain.com;
# SSL Configuration
ssl_certificate /etc/letsencrypt/live/mcp.your-domain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/mcp.your-domain.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
# Security headers
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# Rate limiting
limit_req zone=sse burst=10 nodelay;
# MCP SSE endpoint
location /sse {
# Essential SSE configuration
proxy_pass http://127.0.0.1:8000/sse;
proxy_set_header Connection '';
proxy_http_version 1.1;
proxy_buffering off;
proxy_cache off;
chunked_transfer_encoding off;
# Timeouts for long-lived connections
proxy_read_timeout 300s;
proxy_send_timeout 300s;
proxy_connect_timeout 60s;
# 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 https;
proxy_set_header X-Forwarded-Port 443;
# Access control (adjust as needed)
allow 192.168.1.0/24;
deny all;
}
# Health check endpoint
location /health {
proxy_pass http://127.0.0.1:8000/health;
access_log off;
}
}
# Redirect HTTP to HTTPS
server {
listen 80;
server_name mcp.your-domain.com;
return 301 https://$host$request_uri;
}
# Rate limiting zone definition (place in http block)
# limit_req_zone $binary_remote_addr zone=sse:10m rate=5r/m;Ready-to-use configuration files and Docker Compose setups are available in the examples/mcp-reverse-proxy/ directory, including:
- nginx.conf.example - Production-ready nginx configuration
- Caddyfile.example - Caddy configuration with automatic HTTPS
- traefik.yml.example - Traefik static/dynamic configuration
- docker-compose.sse.yaml - Complete Docker Compose setup with multiple reverse proxy options
After implementing reverse proxy configuration:
- Monitor Performance: Track connection counts, response times, and error rates
- Set Up Logging: Configure detailed logging for debugging and monitoring
- Implement Monitoring: Use tools like Grafana/Prometheus for metrics
- Plan Scaling: Consider load balancing for high-traffic scenarios
- Security Audits: Regular security reviews and updates
For additional support with MCP server deployment, consult the Node Operator Guide and MCP Server Documentation.