Warning, /swf-monitor/apache-swf-monitor.conf is written in an unsupported language. File is not indexed.
0001 #
0002 # SWF Monitor Apache Configuration
0003 #
0004 # Two-backend layout:
0005 # - mod_wsgi (swf-monitor daemon) — /swf-monitor/* except /mcp/
0006 # - mod_proxy → ASGI (uvicorn) — /swf-monitor/mcp/ only
0007 #
0008 # Streaming MCP (StreamableHTTPSessionManager) holds one thread per session under
0009 # WSGI via async_to_sync, so a handful of clients saturates the WSGI pool and
0010 # takes the whole site down. Moving /mcp/ to an ASGI worker isolates that failure
0011 # mode from the rest of the app. See swf-monitor-mcp-asgi.service and
0012 # docs/PRODUCTION_DEPLOYMENT.md.
0013 #
0014 # Static files are served directly by Apache. SSL is handled by the system's
0015 # ssl.conf.
0016
0017 # WSGI Configuration for SWF Monitor
0018 # Tuning:
0019 # threads=30 — absorb bursty concurrency for non-MCP endpoints
0020 # listen-backlog=500 — absorb retry bursts before "listener backlog exceeded"
0021 # queue-timeout=30 — drop queued requests after 30s rather than pile up forever
0022 # inactivity-timeout=300 — recycle daemon if whole pool goes unresponsive for 5 min
0023 # graceful-timeout=15 — bounded graceful shutdown before SIGKILL on reload
0024 # (no request-timeout — would truncate /api/messages/stream/ SSE long-poll)
0025 WSGIDaemonProcess swf-monitor \
0026 python-path=/opt/swf-monitor/current/src:/opt/swf-monitor/current/.venv/lib/python3.11/site-packages \
0027 python-home=/opt/swf-monitor/current/.venv \
0028 processes=1 \
0029 threads=30 \
0030 listen-backlog=500 \
0031 queue-timeout=30 \
0032 inactivity-timeout=300 \
0033 graceful-timeout=15 \
0034 display-name=%{GROUP} \
0035 lang='en_US.UTF-8' \
0036 locale='en_US.UTF-8'
0037
0038 # Environment variables for MCP tools
0039 SetEnv SWF_HOME /opt/swf-monitor
0040
0041 # MCP endpoint on ASGI (uvicorn) worker — streaming HTTP needs async, not WSGI.
0042 # Must appear BEFORE WSGIScriptAlias so the proxy takes precedence for /mcp/.
0043 # See swf-monitor-mcp-asgi.service for the ASGI worker on 127.0.0.1:8001.
0044 #
0045 # Tuning:
0046 # timeout=3600 — allow long-lived streaming MCP sessions (default 60s would cut them)
0047 # keepalive=On — persistent upstream connections for lower per-request overhead
0048 # disablereuse=On — fresh upstream connection per request; avoids stream-reuse races
0049 # proxy-sendchunked — force chunked transfer encoding for streaming responses
0050 # no-gzip — mod_deflate must not buffer/compress streaming responses
0051 # CacheDisable — mod_cache must not store MCP responses
0052 <Location /swf-monitor/mcp/>
0053 ProxyPass http://127.0.0.1:8001/swf-monitor/mcp/ timeout=3600 keepalive=On disablereuse=On
0054 ProxyPassReverse http://127.0.0.1:8001/swf-monitor/mcp/
0055 SetEnv proxy-sendchunked 1
0056 SetEnv no-gzip 1
0057 RequestHeader set X-Forwarded-Proto "https"
0058 CacheDisable on
0059 </Location>
0060
0061 WSGIScriptAlias /swf-monitor /opt/swf-monitor/current/src/swf_monitor_project/wsgi.py process-group=swf-monitor
0062
0063 # Ensure Authorization header is passed through to Django (needed for DRF Token auth)
0064 WSGIPassAuthorization On
0065
0066 # Static files served directly by Apache
0067 Alias /swf-monitor/static /opt/swf-monitor/shared/static
0068 <Directory /opt/swf-monitor/shared/static>
0069 Require all granted
0070 </Directory>
0071
0072 # WSGI script permissions
0073 <Directory /opt/swf-monitor/current/src/swf_monitor_project>
0074 <Files wsgi.py>
0075 Require all granted
0076 </Files>
0077 </Directory>
0078
0079 # Security headers for SWF Monitor
0080 <Location /swf-monitor>
0081 Header always set X-Content-Type-Options nosniff
0082 Header always set X-Frame-Options DENY
0083 Header always set X-XSS-Protection "1; mode=block"
0084 </Location>
0085
0086 <Location /swf-monitor/api/messages/stream/>
0087 Header set Cache-Control "no-cache"
0088 </Location>
0089
0090 # Allow backend token auth on API endpoints; avoid OIDC login redirects
0091 <LocationMatch "^/swf-monitor/api/">
0092 Require all granted
0093 # If mod_auth_openidc is enabled elsewhere, pass unauthenticated requests through
0094 # so Django/DRF can handle Token auth without redirects. Do not enforce OIDC here.
0095 <IfModule mod_auth_openidc.c>
0096 OIDCUnAuthAction pass
0097 # Intentionally no AuthType/Require here; outer 'Require all granted' applies.
0098 </IfModule>
0099 </LocationMatch>
0100
0101 # Allow unauthenticated access to MCP endpoint for LLM tool integration
0102 <LocationMatch "^/swf-monitor/mcp(?:/|$)">
0103 Require all granted
0104 <IfModule mod_auth_openidc.c>
0105 OIDCUnAuthAction pass
0106 </IfModule>
0107 </LocationMatch>
0108
0109 # Allow unauthenticated access to OAuth 2.0 well-known endpoint for MCP discovery
0110 <LocationMatch "^/swf-monitor/\.well-known/">
0111 Require all granted
0112 <IfModule mod_auth_openidc.c>
0113 OIDCUnAuthAction pass
0114 </IfModule>
0115 </LocationMatch>