Skip to content

Nginx Generation

github-actions[bot] edited this page Apr 26, 2026 · 2 revisions

Nginx Generation

Nginx is the single entry point for all external traffic in a ɳSelf stack. Rather than asking you to write or maintain nginx configuration by hand, nself build generates the full nginx configuration automatically based on which services are enabled, what SSL mode you have selected, and which plugins are installed. The generated files are written to an nginx/ directory in your project root and are consumed by the nginx container at startup.


Generated Directory Structure

nginx/
├── nginx.conf              ← Main config (auto-generated, do not edit)
├── conf.d/
│   └── default.conf        ← HTTP→HTTPS redirect, /health endpoint
├── conf.d-dev/             ← Dev-specific overrides (auto-generated)
├── conf.d-prod/            ← Prod-specific overrides (auto-generated)
├── sites/                  ← Auto-generated service routes (one .conf per service)
│   ├── hasura.conf
│   ├── auth.conf
│   ├── storage.conf
│   └── ...
├── includes/
│   └── rate-limits.conf    ← Rate limiting zones
└── routes/                 ← Plugin-generated routes

nginx.conf is the top-level entry point. It includes everything under conf.d/, conf.d-dev/ or conf.d-prod/ (depending on environment), sites/, includes/, and routes/. Never edit nginx.conf or anything under sites/ directly, those files are overwritten on every nself build.


What You CAN Safely Edit

Files in nginx/conf.d/ are hand-managed and safe to customize. The build system checks for conflicts and skips auto-generating a sites/ config if the same domain already exists in conf.d/. This lets you override a service route with a fully custom configuration without the build clobbering your changes.

For example, if you want to serve api.yourdomain.com with a custom caching policy or non-standard proxy settings, create a file in nginx/conf.d/ targeting that server name. On the next nself build, the generator will detect the conflict, skip writing nginx/sites/hasura.conf for that domain, and print a notice so you know the override is in effect.


Route Structure

Each enabled service gets its own subdomain. The base domain is controlled by the BASE_DOMAIN environment variable.

Service Subdomain Notes
Hasura GraphQL api.{BASE_DOMAIN} WebSocket support, 86400s read timeout
Auth auth.{BASE_DOMAIN} Strict rate limiting (10 req/min)
MinIO API storage.{BASE_DOMAIN} 1000M max body size
MinIO Console storage-console.{BASE_DOMAIN}
Admin dashboard admin.{BASE_DOMAIN} Dev mode: proxies to host machine
Search search.{BASE_DOMAIN}
Email UI mail.{BASE_DOMAIN}
Grafana grafana.{BASE_DOMAIN}
Prometheus prometheus.{BASE_DOMAIN}
Alertmanager alertmanager.{BASE_DOMAIN}

Custom services get routes based on CS_N_ROUTE. Frontend apps get routes based on FRONTEND_APP_N_ROUTE. Both support full subdomain paths or path-based routing depending on your configuration.


SSL Termination

SSL mode is controlled by the SSL_MODE environment variable. All TLS termination happens at the nginx layer, upstream services always receive plain HTTP.

Mode Description
local (default) Self-signed certificates generated by mkcert (preferred) or OpenSSL fallback
custom Provide your own cert and key via SSL_CERT_PATH and SSL_KEY_PATH
letsencrypt Automated Let's Encrypt certificates (production)
none HTTP only , not recommended, disables all redirect logic

For local development, mkcert is strongly preferred because browsers trust the resulting certificate without warnings or bypass prompts. When mkcert is available, nself build automatically collects all service subdomains as Subject Alternative Names (SANs) and issues a single cert covering the entire stack. If mkcert is not installed, the build falls back to OpenSSL for a self-signed cert, functional but not browser-trusted.

For production, letsencrypt mode handles certificate issuance and renewal automatically using the ACME HTTP-01 challenge. All subdomains must be publicly reachable before running nself start in this mode.


Security Headers

The following headers are included by default on all generated routes:

add_header X-Content-Type-Options nosniff;
add_header X-Frame-Options DENY;
add_header X-XSS-Protection "1; mode=block";

A Content-Security-Policy header is intentionally not set at the global nginx level. Plugins and custom services often need to customize CSP per route (for example, a chat plugin embedding media from external sources). Set CSP in the relevant conf.d/ override or within the plugin's injected route configuration.


Rate Limiting

Rate limiting zones are defined in nginx/includes/rate-limits.conf and referenced by each generated service config. The defaults are:

Service Rate Burst
GraphQL API 100 req/min 20
Auth 10 req/min 5
Storage uploads 5 req/min 2
Functions 50 req/min 15
Plugin webhooks 30 req/min 10
Admin dashboard 10 req/sec 10
Custom services 10 req/sec 10

To adjust limits for a specific service, override its route in nginx/conf.d/ with your own limit_req_zone and limit_req directives.


Plugin Route Injection

When plugins are installed, they can declare nginx routes in their plugin manifest. During nself build, the build system reads each installed plugin's manifest and writes the declared routes to nginx/routes/, which is included by nginx.conf automatically.

Plugins can add:

  • Service subdomains, for example, the chat plugin adds chat.{BASE_DOMAIN} pointing to the plugin's container.
  • Webhook endpoints, registered under webhooks.{BASE_DOMAIN}/plugin-name by default.
  • Custom domains, controlled by PLUGIN_{NAME}_WEBHOOK_DOMAIN when a plugin needs a fully independent domain rather than a subdomain of BASE_DOMAIN.

Plugin routes follow the same conflict-detection logic as core service routes. If a plugin's declared server name already exists in conf.d/, the plugin route is skipped and a notice is printed.


Lazy Resolver Pattern

Optional services, those that may not be running at nginx startup, use a DNS-based lazy resolution pattern to prevent nginx from failing to start with an upstream lookup error:

resolver 127.0.0.11 valid=10s;
set $upstream http://admin:3021;
proxy_pass $upstream;

Setting the upstream via a variable defers DNS resolution to request time rather than startup time. Docker's embedded DNS (127.0.0.11) resolves container names dynamically, so if an optional container comes up after nginx is already running, traffic routes to it without requiring a reload.

Core required services, Hasura and Auth, use direct proxy_pass without the variable pattern because they are always running when nginx starts.


See also: Architecture | Compose-Generation | Service-Graph | Home

Home


Getting Started


Commands


Features


Configuration


Plugins (87 + 10 monitoring)

Free (25)
Pro (62)
Planned (26)
  • plugin-audit
  • plugin-blog
  • plugin-checkout
  • plugin-commerce
  • plugin-drm
  • plugin-export
  • plugin-flow
  • plugin-import
  • plugin-ldap
  • plugin-mailgun
  • plugin-media
  • plugin-oauth-providers
  • plugin-pages
  • plugin-postmark
  • plugin-rate-limit
  • plugin-reports
  • plugin-saml
  • plugin-scheduler
  • plugin-sendgrid
  • plugin-sso
  • plugin-subscription
  • plugin-thumb
  • plugin-transcoder
  • plugin-twilio
  • plugin-waf
  • plugin-watermark

Guides


Architecture


Reference


Licensing


Security


Brand


Operations


Contributing


Admin


Changelog

Clone this wiki locally