Repository navigation
Nginx Generation
Nginx is the default entry point for external traffic in a ɳSelf stack. nself build generates its configuration from the enabled services, SSL mode, and installed plugins. The generated files are written to nginx/ in your project root.
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.
Fronted projects write API docs to <PROJECT_NAME>.api-docs.conf in the
fronting stack's served nginx/sites directory. Disabling API docs removes
that file only when it carries the project's generated ownership marker.
The fronting stack's api-docs.conf and other projects' docs stay in place.
An older unprefixed file has no project owner marker; inspect it before
removing it manually during migration.
Each build writes .nself/generated/routes.json, version 1 of the proxy route contract. Its _generated key identifies the file as build output. The file describes the project and environment, HTTP defaults, the default server, rate zones, and every generated core, optional, custom service, frontend, and internal route. Route entries include their source, server names, TLS settings, security headers, blocked paths, upstream locations, rate limits, and any shadowed_by hand-managed file. Routes and zones have stable ordering. Hostnames, ports, and paths are included; credentials are excluded.
The docs site is listed as the core route core:api-docs (server name docs.<BASE_DOMAIN>, HTTPS only) when API_DOCS_ENABLED is not false. Its locations carry static_file (the file nginx serves with alias) and headers_add (the add_header set). Two more optional fields describe proxy targets: a location's upstream may carry path and query. The cli renders set $up_x <target>; proxy_pass $up_x;, so with a variable proxy_pass nginx sends the target's path and query instead of the request URI (the whole URI is replaced, not just the matched prefix). A consumer that cannot do the same must refuse such a route. A target with a fragment (#), user info, a space, or any non-ASCII byte is refused at build time. All four fields are omitted from the JSON when unset.
The nginx renderer reads this model. A route suppressed by a hand-managed conf.d file remains in the JSON with shadowed_by set, while its generated nginx/sites/ file is omitted. Plugin snippets and hand-managed server blocks are in the same file; see Plugin and hand-managed routes.
Duplicate generated domains use the compatibility gate: v1.4 keeps the existing build preflight refusal text for duplicates it already detects and warns once for additional duplicates found by the renderer. With NSELF_V15=1, every duplicate is refused as E055 with both route IDs. Give each service a distinct route before rebuilding.
The installed traefik plugin selects the proxy provider. It must be enabled and installed in a plugin directory inside this project. There is no provider config key. Export an absolute NSELF_PLUGIN_DIR in the shell for nself add, for example /path/to/project/.nself/plugins, and set the same value in the project's .env for builds. Without an enabled traefik plugin, the provider is nginx. Preflight also treats declared traefik in nself.yaml or nself.yml as the provider when the build would auto-install it. NSELF_AUTO_INSTALL_PLUGINS=false disables that auto-install. This check fails closed even if an offline or failed install would have left the project using nginx. For declared traefik, the plan shows nginx removed; Traefik services appear in the re-plan after the plugin installs, through reconcile.Apply's second render.
The build checks the directory's resolved path, including symlinks. E480 refuses traefik in a shared directory such as ~/.nself/plugins, or in the project root itself. A shared install would switch every project using that directory on its next build. Reinstall in a project subdirectory, or run nself remove traefik from the shared directory.
Before persisting secrets or changing file modes, the build checks the real compose discovery result for traefik without rewriting plugin fragments. E480 refuses a symlinked traefik entry or a missing installed Traefik compose fragment. With nginx selected, it only scans real plugin directories for fragments from traefik or traefik.*, including compose-only installs and update leftovers. A disabled traefik plugin is ignored. The scan does not normalise other plugins or surface their discovery warnings and errors during preflight. Remove those Traefik fragments or reinstall traefik. Compose discovery loads real plugin directories only. Install the plugin as a real directory inside this project. Relative NSELF_PLUGIN_DIR paths resolve against the process working directory, including when it differs from the project directory. A leading ~/ is a literal directory name, not a home-directory expansion. Export an absolute NSELF_PLUGIN_DIR for nself add and set it in .env so builds from another working directory find the same plugin.
Fronted mode is nginx-only. E482 refuses traefik when NGINX_FRONTED_BY is set. It also refuses a fronting stack whose generated docker-compose.yml has no nginx service or whose .nself/compose-files.txt lists a traefik fragment. Keep nginx in both projects, or unset NGINX_FRONTED_BY and build this project with its own proxy.
On Linux, with traefik selected, the build writes NSELF_HOST_UID and NSELF_HOST_GID (the ids of the user running the build, who owns the generated 0600 private keys) to .nself/compose.env. The traefik fragment runs as that user with every capability dropped except NET_BIND_SERVICE, so it can read the keys. The build also creates .nself/generated/traefik and .nself/generated/traefik-static as that user before Docker can create them root-owned, and the one-shot traefik-config renderer runs as the same user, so swapping back to nginx removes both without sudo. Other hosts and nginx projects get no such keys, and the fragment falls back to 0:0.
E481 lists every item that prevents a complete route swap:
- Route directives in
unmodelled, routes withshadowed_by, and entries inunmodelled_global. - Generated upstream URLs that cannot be parsed, have a path other than
/, or have a query. Schemeless upstreams are checked as HTTP URLs. The version 1 contract cannot carry paths or queries (D-0310). - Foreign files in
nginx/sites/, including hand-written files and marked files from another build that this build will not write.
Remove or model the named items, or keep nginx with nself remove traefik. Both nginx/ and .nself/generated/routes.json are always generated. With traefik selected, compose omits the nginx service. The plugin owns Traefik configuration and serves ports 80 and 443 through its file provider.
Enabled API docs are an accepted difference (D-0310), not an E481 refusal. Traefik does not serve docs.<base>, because the docs host is absent from the version 1 route contract. Each build warns once and still writes its own nginx/sites/api-docs.conf. The config loader does not parse any API_DOCS_* variables (D-0455), so environment-only projects cannot disable docs through those variables. The e2e parity matrix excludes the docs host.
Use this sequence to switch a running project:
nself add traefik --yesThe add writes files through reconcile, then, in the same apply, removes the nginx orphan and starts the traefik services that have no container yet. The plan lists the orphan removal before anything runs. Prod-class changes print the plan and require confirmation. When nothing is orphaned, the add starts nothing; run nself start as before. If the removal or the start fails, the files stay written and the error names the fallback: nself build --remove-orphans --yes, then nself start. A refused add leaves generated files and containers unchanged, but leaves traefik installed. Remove it with nself remove traefik, or fix the refusal before the next build. For measured downtime, see the e2e evidence.
nself remove traefik --yesThe remove deletes the plugin, removes the traefik containers as orphans and starts nginx in the same apply (the fallback is nself build --remove-orphans --yes, then nself start). The next build derives nginx again and deletes the renderer's two output directories, .nself/generated/traefik/ and .nself/generated/traefik-static/, but only when the renderer's .nself-generated marker is inside each. A directory without that marker, a directory reached through a symlink, and every other path stay in place; the build prints one warning and carries on. The build also removes the traefik containers as orphans. After the sequence, the generated files, the compose service set and the containers match the project as it was before nself add traefik, and the request-parity matrix passes through nginx again.
On Linux the traefik-config container runs as root and Docker creates its bind directories root-owned, so an unprivileged build cannot delete them. The build then warns (naming the directory), keeps the marker so the next build retries, and does not fail. Run the sudo rm -rf it prints, or sudo chown -R "$USER" .nself/generated, and build again. This is debt D-0782; the plugin's one-shot should run as the host user.
Between the build that deletes the rendered files and the removal of the traefik container, a still-running traefik serves no routes (it watches the deleted directory). Nginx cannot bind ports 80 and 443 before that container is gone, so the swap back has the same short downtime window as the swap in.
If the remove is interrupted (a crash, kill -9), the generated files are already final, because the build writes them before it removes any container. Run nself build --remove-orphans --yes again (it is idempotent and removes whatever containers the interruption left), then nself start to bring nginx back. nself start alone does not remove orphaned containers, so a leftover traefik one-shot container would stay until that build. Both paths are tested on Linux by scripts/e2e/proxy-swap.sh --round-trip --assert-identical and --kill-mid-remove.
routes.json also describes the nginx files nself does not render itself: every nginx/*.conf a plugin ships (rendered into nginx/sites/<plugin>-<file>, see Plugin Route Injection) and every hand-managed nginx/conf.d/*.conf and nginx/conf.d-<env>/*.conf (not default.conf). Each server { } block becomes one route:
| Route | source |
id |
file |
|---|---|---|---|
| Plugin snippet | plugin |
plugin:<plugin>/<file>#<k> |
nginx/sites/<plugin>-<file> |
| Hand-managed file | hand_managed |
hand:<file>#<k> (hand:<conf.d-env>/<file>#<k> for an env directory) |
nginx/conf.d/<file> |
Two plugins whose <plugin>-<file> names resolve to the same nginx/sites file (compared case-insensitively, for example plugin a-b with c.conf and plugin a with b-c.conf) are refused as E055 with NSELF_V15=1. In v1.4 mode it is a warning and the later file replaces the earlier one.
<k> is the 1-based index of the server block in the file. A usual snippet has two blocks for one name (port 80 redirect, port 443 proxy), so one name can appear in two routes. nginx behaviour does not change: plugin files are still copied byte for byte and hand-managed files are never touched.
Each directive is read with a closed vocabulary. It is mapped to a model field, judged to have no observable effect, or copied word for word into the route's unmodelled list. Nothing is evaluated: no include, map or if, and a variable is substituted only for set $v <url>; followed by proxy_pass $v; in the same location or server. deny all and limit_except map only in a closed scope: the location has no allow of its own (also inside if or limit_except), no satisfy other than all is in effect (from the location, the server or the top of the file), and neither the location nor its server holds an if, rewrite, break, return, include or error_page. An error_page at the top of the file counts too, because nginx inherits it into every location. Any of these can let a request past the access rule or answer it first.
| Directive | Result |
|---|---|
listen 80, listen 443 ssl [http2] (also [::]: and 0.0.0.0: forms) |
listen.http, listen.https
|
server_name (plain FQDNs, lower-cased: nginx matches names without case) |
server_names; a wildcard, regex, _ or variable name keeps the directive in unmodelled
|
ssl_certificate and ssl_certificate_key both under /etc/nginx/ssl/<dir>/ with names fullchain.pem and privkey.pem, where <dir> starts with a letter or digit |
tls.ssl_dir; a different path or file name, mismatched directory names, a missing certificate or key, or a directory named . or .. is unmodelled
|
ssl_protocols, ssl_ciphers
|
tls.protocols, tls.ciphers (empty and null mean the http-level default) |
return 30x https://$host$request_uri; |
return on a / location and http_to_https_redirect: true; another redirect target with a variable is mapped and also unmodelled; return CODE; maps to return; a body text stays unmodelled
|
location /p and location = /p
|
locations with match prefix or exact; regex, ^~, named, nested and repeated locations stay unmodelled whole |
proxy_pass http://host:port or an upstream name with one server
|
upstream; a URI part (http://h:1/v1/) is mapped AND kept in unmodelled, because v1 cannot carry the path |
the four standard forwarded headers (Host, X-Real-IP, X-Forwarded-For, X-Forwarded-Proto, all present) |
forwarded_headers; a partial set stays unmodelled
|
Upgrade $http_upgrade with Connection "upgrade" (or "Upgrade") and proxy_http_version 1.1 in scope |
websocket; without proxy_http_version 1.1, the headers are recorded in unmodelled
|
other proxy_set_header with a literal value |
headers_set; a value with a variable stays unmodelled. An empty value (proxy_set_header Authorization "";) is kept as "" and means "do not pass this header to the upstream", as in nginx |
proxy_connect_timeout, proxy_read_timeout, proxy_send_timeout
|
timeouts in whole seconds; a sub-second or zero value stays unmodelled
|
client_max_body_size with k, m, g
|
max_body_bytes; 0 (unlimited) stays unmodelled
|
access_log off |
access_log: false |
deny all in a closed scope (defined above the table) |
deny_all; otherwise unmodelled
|
limit_except M... { deny all; } in a closed scope |
methods as written (nginx also allows HEAD when GET is listed); otherwise unmodelled
|
keepalive, proxy_http_version (except when it enables websocket), proxy_buffer_size, proxy_buffers, proxy_busy_buffers_size, http2, resolver, a set used only for proxy_pass
|
no observable effect; not recorded |
| anything else |
unmodelled, verbatim |
Timeouts, client_max_body_size, access_log and proxy_set_header written at server level apply to every location that does not set its own, as in nginx. A server block with no listen listens on port 80, as in nginx. A location with return plus proxy_pass, or return plus deny all, maps both and also keeps the return in unmodelled (nginx always answers the return first, before the proxy and before the access rules; v1 does not say which wins). The reader splits words only at space, tab, CR and LF, as nginx does: form feed and vertical tab are ordinary word bytes. Variable names are case-insensitive, as in nginx. A set variable is not substituted into proxy_pass when it is set twice, when the location or server holds an if, rewrite, break, return, include or error_page, when it is a server-level set and some location of that server sets the same variable, or when another directive can write it: auth_request_set, set_by_lua and the like, or any variable named outside the server blocks (map, geo, js_set ...). Every top-level upstream is kept verbatim in unmodelled_global, even when its name resolves inside the file, because nginx upstreams are visible to every file; a proxy_pass to an upstream name defined in another file is unmodelled, never resolved as a DNS host.
unmodelled is the fail-closed list for other providers: a route whose unmodelled is not empty has behaviour the model cannot say, so a provider that is not nginx must refuse it, never serve a partial route. The same holds for the top-level unmodelled_global list, which holds the directives outside any server block: map, limit_req_zone, an upstream with several servers, and bare location blocks in hand-managed files. A hand-managed file that does not parse is recorded there too, and so is each plugin file the build left out of nginx/sites (skipped plugin <plugin>/<file>: <reason>, see Plugin Route Injection). Snippets are third-party input; the reader is fuzzed and a test checks that every directive is mapped, ignored or recorded.
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.
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 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.
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 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.
There is no NGINX_RATE_LIMIT variable — it does not exist anywhere in
this codebase, is not declared in any env template, and no generator reads
it. Each zone above is configured via its own dedicated var instead:
RATE_LIMIT_API_RPS, RATE_LIMIT_AUTH_RPS, RATE_LIMIT_AI_RPS
(internal/nginx/ratelimit.go) and AUTH_RATE_LIMIT for the whole-server
Auth zone.
On top of the one server-wide zone above, every generated service conf
(nginx/sites/*.conf) and every proxying nself ssl add <domain> --upstream ... custom-domain conf (nginx/conf.d/custom-*.conf) also
carries two path-scoped location blocks, applied unconditionally
regardless of which zone the server-wide one uses:
| Path | Match | Zone | Rate | Burst |
|---|---|---|---|---|
/auth/login |
exact (location =) |
auth_strict |
RATE_LIMIT_AUTH_RPS (default 5r/s) |
5 |
/api/ |
prefix | api |
RATE_LIMIT_API_RPS (default 30r/s) |
20 |
nginx resolves the longest-matching prefix location regardless of
declaration order in the file, so a request to either path always hits the
stricter zone even on a server otherwise routed through a looser one (for
example a CS_N custom service on the Custom services zone above) — a
request that never touches those paths is unaffected. Applied
unconditionally because the generator has no reliable way to know whether
a given CS_N or internal-route service's upstream actually serves either
path; the blocks are harmless when it doesn't. The custom-domain
placeholder conf (nself ssl add <domain> with no --upstream) never
proxies anywhere, so it carries neither block.
Source: internal/nginx/generator.go's defaultSecurityPathZones()
(default set) and ServiceRouteData.PathZones (template field), rendered
by internal/nginx/templates/service.conf.tmpl; custom-domain equivalent
in cmd/commands/ssl_install.go's writeCustomDomainConf(). Verified by
nself doctor --deep's SEC-HARDENING-06 check
(internal/doctor/hardening_check_nginx_zones.go), which accepts either of
two signals per generated conf file — it does not require both:
-
Service identity (cli#379): the file is split into its
server {}blocks, and a block whoseserver_namefirst label is a known auth/API hostname (authfor the auth service;api,hasura,graphql,ping, orping-apifor the API/Hasura/ping-api surface — seenginx.Routedefaults ininternal/nginx/routes_core.goand the shippedCS_1_ROUTE=pingexample ininternal/setup/setup_env_files.go) contains alimit_reqdirective anywhere in its body, including the server-widelocation /zone from the table above — not only the path-scoped blocks. -
Literal path fallback (original behavior): the file contains
limit_reqco-occurring with the literal string/auth/loginor/api/, for hand-written gateway configs that route by path instead of byserver_name.
A conf with no limit_req anywhere fails both signals, and a limit_req
confined to an unrelated service's block (e.g. only a frontend app's
location /) satisfies neither.
An installed plugin can ship nginx server blocks as nginx/*.conf in its plugin directory (NSELF_PLUGIN_DIR, default ~/.nself/plugins). During nself build each file is rendered and written to nginx/sites/<plugin>-<file> (for a fronted project, the fronting stack's nginx/sites), which nginx.conf includes at the http level.
Rendering replaces these variables and nothing else:
| Variable | Value |
|---|---|
${BASE_DOMAIN} |
BASE_DOMAIN |
${SSL_DIR} |
the base domain's certificate dir under the nginx ssl mount, certificates/<domain-dir> (local.nself.org gives certificates/local-nself-org), so /etc/nginx/ssl/${SSL_DIR}/fullchain.pem is the certificate ɳSelf serves |
${PROJECT_NAME}, ${COMPOSE_PROJECT_NAME}
|
PROJECT_NAME |
${DOCKER_NETWORK} |
the stack's Docker network |
${POSTGRES_USER}, ${POSTGRES_DB}, ${POSTGRES_PASSWORD}, ${HASURA_PORT}, ${AUTH_PORT}, ${NSELF_PLUGIN_DIR}
|
the matching project values |
A file nginx would refuse to load would stop nginx, and with it every route, so the build leaves it out and prints a warning naming the plugin, the file and the reason:
- it still holds a
${NAME}variable from outside this table (nginx's own lower-case variables such as${host}are fine); - it does not parse, or it has a directive at the top level that nginx accepts only inside a
serverblock (a barelocationblock, for example); - it loads the
${SSL_DIR}certificate while the build serves no TLS (SSL_MODE=none, orletsencrypt/custombefore the certificate exists).
A skipped file is also listed in routes.json under unmodelled_global. Run nself build again after updating the plugin or issuing the certificate.
A plugin file whose server_name is already claimed on the same port by another file in nginx/sites fails the build, naming both files, because nginx would silently serve only one of them.
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
Getting Started
Commands
- Commands, Overview
- Lifecycle: cmd-init · cmd-build · cmd-start · cmd-stop · cmd-restart · cmd-dev
- Monitoring: cmd-status · cmd-logs · cmd-health · cmd-urls · cmd-doctor · cmd-monitor · cmd-alerts · cmd-sentry · cmd-watchdog
- Data: cmd-db · cmd-backup · cmd-dr · cmd-queue · cmd-webhooks
- Config: cmd-config · cmd-service · cmd-env · cmd-promote
- Networking: cmd-ssl · cmd-trust · cmd-dns-setup
- Security: cmd-access · cmd-security · cmd-secrets
- Tenancy: cmd-tenant · cmd-billing
- Plugins: cmd-plugin · cmd-license · cmd-dogfood (extracted, CLI-R11) · cmd-k8s (extracted, CLI-R11) · cmd-encryption (extracted, CLI-R11) · cmd-waf (extracted, CLI-R11) · cmd-federation (extracted, CLI-R11) · cmd-mail (extracted, CLI-R11) · cmd-dlq (extracted, CLI-R11)
- AI: cmd-ai · cmd-claw · cmd-model
- Templates: cmd-template
- Utilities: cmd-exec · cmd-clean · cmd-reset · cmd-update · cmd-upgrade · cmd-version · cmd-admin · cmd-migrate · cmd-migrate-firebase · cmd-migrate-supabase · cmd-completion
Features
- Features, Overview
- Feature-Auth
- Feature-Storage
- Feature-Search
- Feature-Functions
- Feature-Email
- Feature-Monitoring
- Feature-Plugins
- Feature-nClaw, AI Assistant
- Feature-nChat, Messaging
- Feature-nFamily, Family Social
- Feature-nCloud, Managed Hosting
- Feature-Memory-Rooms, Knowledge Organization
- Feature-Agent-Dashboard, Agent Metrics
- Feature-Image-Generation, AI Image Generation
Configuration
- Configuration, Overview
- Config-Env-Vars
- Config-Postgres
- Config-Hasura
- Config-Auth
- Config-Nginx
- Shared-Proxy
- Config-Optional-Services
- Config-Custom-Services
- Config-System
Plugins (87 + 10 monitoring)
- Plugin-Overview
- Plugin-Catalog
- Plugin-Install
- Plugin-Licensing
- Plugin-Architecture
- Plugin-Dev-Guide
- Plugin-Migrations-Contract
Free (25)
- plugin-backup
- plugin-content-acquisition
- plugin-content-progress
- plugin-cron
- plugin-donorbox
- plugin-feature-flags
- plugin-github
- plugin-github-runner
- plugin-invitations
- plugin-jobs
- plugin-link-preview
- plugin-mdns
- plugin-mlflow
- plugin-monitoring
- plugin-notifications
- plugin-notify
- plugin-paypal
- plugin-search
- plugin-shopify
- plugin-stripe
- plugin-subtitle-manager
- plugin-tokens
- plugin-torrent-manager
- plugin-vpn
- plugin-webhooks
Pro (62)
- plugin-access-controls
- plugin-activity-feed
- plugin-admin-api
- plugin-analytics
- plugin-auth
- plugin-bots
- plugin-browser
- plugin-calendar
- plugin-cdn
- plugin-chat
- plugin-claw
- plugin-claw-budget
- plugin-claw-news
- plugin-claw-web
- plugin-cloudflare
- plugin-cms
- plugin-compliance
- plugin-ddns
- plugin-devices
- plugin-documents
- plugin-entitlements
- plugin-file-processing
- plugin-game-metadata
- plugin-geocoding
- plugin-geolocation
- plugin-google
- plugin-home
- plugin-idme
- plugin-knowledge-base
- plugin-linkedin
- plugin-livekit
- plugin-media-processing
- plugin-meetings
- plugin-moderation
- plugin-mux
- plugin-object-storage
- plugin-observability
- plugin-photos
- plugin-podcast
- plugin-post
- plugin-realtime
- plugin-retro-gaming
- plugin-rom-discovery
- plugin-social
- plugin-sports
- plugin-support
- plugin-tmdb
- plugin-voice
- plugin-web3
- plugin-workflows
Planned (26)
plugin-auditplugin-blogplugin-checkoutplugin-commerceplugin-drmplugin-exportplugin-flowplugin-importplugin-ldapplugin-mailgunplugin-mediaplugin-oauth-providersplugin-pagesplugin-postmarkplugin-rate-limitplugin-reportsplugin-samlplugin-schedulerplugin-sendgridplugin-ssoplugin-subscriptionplugin-thumbplugin-transcoderplugin-twilioplugin-wafplugin-watermark
Guides
- Guide-Production-Deployment
- Guide-SSL-Setup
- Guide-Multi-Tenancy
- Guide-Security-Hardening
- Guide-Monitoring-Setup
- Guide-Backup-Restore
- Guide-Custom-Services
- Guide-Migration-from-v1
Architecture
- Architecture
- Architecture-Guardrails
- Service-Graph
- Compose-Generation
- Nginx-Generation
- Plugin-Architecture
Reference
Machine interface
-
JSON-Output,
--jsonenvelope -
Command-Registry,
nself help --json - Exit-Codes
- error-codes, Error Codes
-
Compat-V15,
NSELF_V15gate
Licensing
Security
Brand
Operations
- operations/release-cascade, Release Cascade
- operations/self-healing, Self-Healing Schema
- operations/redis-tuning, Redis Pool Tuning
- operations/meilisearch-warmup, MeiliSearch Warm-Up
- operations/jwt-rotation, JWT Key Rotation
- operations/windows-wsl2-setup, Windows / WSL2 Setup
- operations/gemini-oauth-reauth, Gemini OAuth Reauth
Contributing
Admin
- USER-ACTION-QUEUE, Pending Admin Actions
All commands (21)
- A: cmd-add · cmd-admin
- B: cmd-backup · cmd-build
- C: cmd-clean · cmd-config
- D: cmd-db · cmd-deploy · cmd-doctor
- E: cmd-exec
- I: cmd-init
- L: cmd-license · cmd-logs
- M: cmd-mcp
- R: cmd-remove · cmd-reset · cmd-restart
- S: cmd-start · cmd-status · cmd-stop
- U: cmd-update