Skip to content

YAML Configuration Reference

Complete reference for all Navigator configuration options.

Configuration Structure Overview

Navigator's YAML configuration is organized into logical sections:

server:                    # HTTP server settings
  listen: 3000
  hostname: "localhost"
  root_path: "/prefix"
  static:                  # Static file configuration
    public_dir: "./public"
    allowed_extensions: [...]
    try_files: [...]
    cache_control: {...}
  idle:                    # Fly.io machine idle management
    action: suspend
    timeout: 20m

auth:                      # Authentication (top-level)
  enabled: true
  realm: "Restricted"
  htpasswd: "./htpasswd"
  public_paths: [...]

maintenance:               # Maintenance page configuration
  page: "/503.html"

applications:              # Multi-tenant app configuration
  pools: {...}
  framework: {...}
  env: {...}
  tenants: [...]

managed_processes:         # External process management
  - name: redis
    command: redis-server
    ...

routes:                    # URL routing and rewriting
  rewrites: [...]
  reverse_proxies: [...]
  fly:                     # Fly.io-specific routing
    sticky_sessions: {...}
    replay: [...]

hooks:                     # Lifecycle hooks
  server: {...}
  tenant: {...}

logging:                   # Logging configuration
  format: json
  file: "..."
  vector: {...}

server

HTTP server configuration.

server:
  listen: 3000                    # Port to listen on (required)
  hostname: "localhost"           # Hostname for requests (optional)
  root_path: "/showcase"          # Root URL path prefix (optional)

  # Static file configuration
  static:
    public_dir: "./public"        # Directory for static files (optional)
    allowed_extensions:           # Allowed file extensions (optional)
      - html
      - css
      - js
      - png
      - jpg
    try_files:                    # Try files suffixes (optional)
      - index.html
      - .html
      - .htm
    cache_control:
      default: "1h"               # Default cache duration
      overrides:                  # Path-specific cache durations
        - path: "/assets/"
          max_age: "24h"

  # Machine idle configuration (Fly.io)
  idle:
    action: suspend               # "suspend" or "stop"
    timeout: 20m                  # Duration: "30s", "5m", "1h30m"
Field Type Default Description
listen integer/string 3000 Port to bind HTTP server
hostname string "" Hostname for Host header matching
root_path string "" Root URL path prefix (e.g., "/showcase")

server.static

Static file serving configuration.

server:
  static:
    public_dir: "./public"
    allowed_extensions: [html, css, js, png, jpg]
    try_files: [index.html, .html, .htm]
    cache_control:
      default: "1h"
      overrides:
        - path: "/assets/"
          max_age: "24h"
        - path: "/images/"
          max_age: "12h"
Field Type Default Description
public_dir string "./public" Directory for static files
allowed_extensions array [] File extensions allowed (empty = all allowed)
try_files array [] Suffixes to try when resolving paths
cache_control object - Cache header configuration
cache_control.default string "" Default cache duration (e.g., "1h")
cache_control.overrides array [] Path-specific cache configurations
cache_control.overrides[].path string - URL path prefix to match
cache_control.overrides[].max_age string - Cache duration (e.g., "24h")

Allowed Extensions: If omitted or empty, all files in public_dir can be served. If specified, only files with these extensions can be served.

Try Files: When present, Navigator attempts each suffix in order before falling back to the application.

Example: For request /studios/boston with try_files: [index.html, .html, .htm]: 1. Try public/studios/boston (exact match) 2. Try public/studios/boston/index.html 3. Try public/studios/boston.html 4. Try public/studios/boston.htm 5. Fall back to application

server.idle

Machine auto-suspend/stop configuration (Fly.io only).

Field Type Default Description
action string "" Action to take: "suspend" or "stop"
timeout string "" Idle duration before action (e.g., "20m", "1h")

auth

Authentication configuration using htpasswd files.

auth:
  enabled: true                   # Enable/disable authentication
  realm: "Restricted"             # Authentication realm name
  htpasswd: "./htpasswd"          # Path to htpasswd file
  public_paths:                   # Paths that bypass authentication
    - "/assets/"
    - "/favicon.ico"
    - "*.css"
    - "*.js"
Field Type Default Description
enabled boolean false Enable authentication
realm string "Restricted" Basic Auth realm displayed in browser
htpasswd string "" Path to htpasswd file
public_paths array [] Glob patterns for paths that bypass auth

Supported htpasswd Formats

  • APR1 (Apache MD5)
  • bcrypt
  • SHA
  • MD5-crypt
  • Plain text (not recommended)

maintenance

Maintenance page configuration.

maintenance:
  page: "/503.html"               # Path to maintenance page (within public_dir)
Field Type Default Description
page string "/503.html" Path to custom maintenance page

Navigator automatically serves a maintenance page when: - An application is starting and exceeds the startup_timeout - A Fly-Replay target is unavailable - Sticky session routing fails to find the target machine

The maintenance page is served from {server.static.public_dir}/{maintenance.page} (e.g., public/503.html). If this file doesn't exist, Navigator serves a default maintenance page.

Recommended 503.html:

<!DOCTYPE html>
<html>
<head>
  <title>Service Starting (503)</title>
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <meta http-equiv="refresh" content="5">  <!-- Auto-refresh every 5 seconds -->
  <style>
    body {
      font-family: arial, sans-serif;
      text-align: center;
      padding: 50px;
    }
  </style>
</head>
<body>
  <h1>Service Starting...</h1>
  <p>The application is starting up. This page will refresh automatically.</p>
</body>
</html>

The <meta http-equiv="refresh" content="5"> tag automatically reloads the page every 5 seconds, so users don't need to manually refresh while waiting for the application to become ready.

applications

Application configuration for multi-tenant deployments.

applications:
  # Process pool configuration
  pools:
    max_size: 10                  # Maximum app processes
    timeout: 5m                   # Idle timeout (duration format)
    start_port: 4000              # Starting port for allocation
    default_memory_limit: "512M"  # Default memory limit per tenant (Linux only)
    user: "rails"                 # Default user to run tenants as (Unix only)
    group: "rails"                # Default group to run tenants as (Unix only)

  # Startup configuration
  health_check: "/up"             # Default health check endpoint
  startup_timeout: "5s"           # Timeout before showing maintenance page

  # WebSocket connection tracking (default: true)
  track_websockets: true          # Track WebSocket connections globally

  # Framework configuration (optional, can be per-tenant)
  framework:
    command: bin/rails
    args: ["server", "-p", "${port}"]
    app_directory: "/rails"
    port_env_var: PORT
    start_delay: 2s

  # Environment variable templates
  env:
    DATABASE_URL: "sqlite3://db/${database}.sqlite3"
    RAILS_ENV: production

  # Individual tenant applications
  tenants:
    - path: "/tenant1/"           # URL path prefix (required)
      var:                        # Template variables
        database: tenant1
      env:                        # Tenant-specific environment
        TENANT_NAME: "Tenant 1"

      # Optional tenant-specific overrides
      root: "/app"                # Application directory
      public_dir: "public"        # Public files directory
      framework: rails            # Framework type
      runtime: bundle             # Runtime command
      server: exec                # Server command
      args: ["puma", "-p", "${port}"]  # Server arguments
      health_check: "/health"     # Override: custom health check endpoint
      startup_timeout: "10s"      # Override: wait longer for this tenant
      track_websockets: false     # Override: disable WebSocket tracking
      memory_limit: "1G"          # Override: memory limit for this tenant (Linux only)
      user: "app"                 # Override: user for this tenant (Unix only)
      group: "app"                # Override: group for this tenant (Unix only)

      # Tenant-specific lifecycle hooks
      hooks:
        start:
          - command: /app/bin/tenant-init.sh
            timeout: 30s
        stop:
          - command: /app/bin/tenant-cleanup.sh
            timeout: 30s

applications.pools

Process pool management for tenant applications.

Field Type Default Description
max_size integer 10 Maximum number of app processes
timeout string "5m" Idle timeout (duration: "5m", "10m")
start_port integer 4000 Starting port for dynamic allocation
default_memory_limit string "" Default memory limit (e.g., "512M", "1G") - Linux only, requires root
user string "" Default user to run tenant processes as - Unix only
group string "" Default group to run tenant processes as - Unix only

Memory Limits (Linux only): - Requires running Navigator as root on Linux with cgroups v2 - Uses Linux cgroups to enforce per-tenant memory limits - When a tenant exceeds its limit, the kernel OOM kills only that tenant - OOM-killed tenants are removed from the registry and restart on next request - Cgroups persist during idle timeout, only cleaned up on Navigator shutdown - Supported formats: "512M", "1G", "2048M", "1.5G" - On non-Linux or non-root: Configuration is ignored (graceful degradation)

User/Group Credentials (Unix only): - Runs tenant processes as specified non-root user for security isolation - Navigator must run as root to drop privileges to specified user/group - Enhances security by limiting tenant process permissions - On Windows or when not running as root: Configuration is ignored

applications.health_check

Global default health check endpoint for application readiness detection. Can be overridden per-tenant.

Field Type Default Description
health_check string "/" HTTP endpoint for health checks (e.g., "/up", "/health")

applications.startup_timeout

Global timeout for waiting for applications to become ready before serving the maintenance page. Can be overridden per-tenant.

Field Type Default Description
startup_timeout string "5s" Duration to wait for app startup (e.g., "5s", "10s", "30s")

When an application is starting, Navigator waits up to this duration for the health check to pass. If the timeout is reached, Navigator serves the configured maintenance page (typically public/503.html) instead of returning a 502 error. The maintenance page can include <meta http-equiv="refresh" content="5"> to auto-refresh.

Example:

applications:
  startup_timeout: "10s"    # Wait 10 seconds globally

  tenants:
    - name: slow-app
      path: /slow/
      startup_timeout: "30s"  # Override: wait 30 seconds for this tenant

applications.track_websockets

Global setting for WebSocket connection tracking. When enabled, Navigator tracks active WebSocket connections to prevent apps from shutting down during idle timeouts. Can be overridden per-tenant.

Field Type Default Description
track_websockets boolean true Track WebSocket connections to prevent idle shutdown

When to disable: Tenants that proxy WebSockets to standalone servers (e.g., separate Action Cable) or don't handle WebSockets directly.

applications.framework

Default framework configuration (can be overridden per-tenant).

Field Type Default Description
command string "" Framework command (e.g., "bin/rails")
args array [] Command arguments with ${port} substitution
app_directory string "" Application root directory
port_env_var string "PORT" Environment variable for port number
start_delay string "0s" Delay before starting (duration format)

applications.env

Environment variable templates with ${variable} substitution from tenant var values.

applications.tenants

List of tenant applications.

Field Type Required Description
path string URL path prefix (must start/end with /)
var object Template variables for env substitution
env object Tenant-specific environment variables
root string Application root directory
public_dir string Public files directory
framework string Framework type override
runtime string Runtime command override
server string Server command override
args array Server arguments override
health_check string Health check endpoint override (e.g., "/up")
track_websockets boolean Override WebSocket tracking (nil = use global)
memory_limit string Memory limit override (e.g., "1G") - Linux only
user string User override (runs as this user) - Unix only
group string Group override (runs as this group) - Unix only
hooks object Tenant-specific lifecycle hooks

Note: The name field is automatically derived from the path (e.g., /showcase/2025/boston/2025/boston).

Per-Tenant Memory Limits: Useful for tenants with different resource requirements. For example, a large event might use memory_limit: "1G" while smaller events use the pool default of 512M.

managed_processes

External processes managed by Navigator.

managed_processes:
  - name: redis                   # Process identifier
    command: redis-server         # Command to execute
    args: ["--port", "6379"]     # Command arguments
    working_dir: "/var/lib/redis" # Working directory
    env:                         # Environment variables
      REDIS_PORT: "6379"
    auto_restart: true           # Restart on crash
    start_delay: 0               # Delay before starting (seconds)
Field Type Required Description
name string Unique process identifier
command string Command to execute
args array Command arguments
working_dir string Working directory
env object Environment variables
auto_restart boolean Restart process on crash
start_delay integer Delay before starting (seconds)

routes

URL routing and rewriting rules.

routes:
  rewrites:                      # URL rewrite rules
    - pattern: "^/old/(.*)"      # Regex pattern
      replacement: "/new/$1"     # Replacement string
      redirect: true             # HTTP redirect vs rewrite
      status: 301               # HTTP status code

  reverse_proxies:               # Reverse proxy routes
    - name: "api-backend"        # Route name
      path: "^/api/"             # URL pattern (regex)
      target: "https://api.example.com"  # Target URL
      strip_path: true           # Remove prefix before proxying
      headers:                   # Custom headers
        X-Forwarded-Host: "$host"

  fly:                           # Fly.io-specific routing
    sticky_sessions:             # Cookie-based session affinity
      enabled: true
      cookie_name: "_navigator_machine"
      cookie_max_age: "2h"
      cookie_secure: true
      cookie_httponly: true
      cookie_samesite: "Lax"
      cookie_path: "/"
      paths:                     # Optional: specific paths for sticky sessions
        - "/app/*"

    replay:                      # Fly-Replay routing
      - path: "^/api/"           # URL pattern
        region: "syd"            # Target region
        app: "my-app"            # Target app
        machine: "abc123"        # Target machine ID
        status: 307             # HTTP status
        methods: [GET, POST]    # HTTP methods

rewrites

Field Type Required Description
pattern string Regular expression pattern
replacement string Replacement string
redirect boolean Send HTTP redirect vs internal rewrite
status integer HTTP status code for redirects

reverse_proxies

Reverse proxy routes to external services.

Field Type Default Required Description
name string - Descriptive name for the route
path string - Regular expression pattern (alternative to prefix)
prefix string - Simple prefix match (alternative to path)
target string - Target URL (supports $1, $2 for capture groups)
strip_path boolean false Remove matched prefix before proxying
headers object - Custom headers (supports $host, $remote_addr, $scheme)
websocket boolean false Enable WebSocket proxying

Note: Either path (regex) or prefix (simple string) must be specified, but not both.

Capture Group Substitution:

Use regex capture groups in path and reference them in target with $1, $2, etc.

routes:
  reverse_proxies:
    # Single capture group
    - name: user-api
      path: "^/users/([0-9]+)$"
      target: "https://api.example.com/v1/user/$1"

    # Multiple capture groups
    - name: archive-proxy
      path: "^/archive/([0-9]{4})/([0-9]{2})/(.*)$"
      target: "https://archive.example.com/$1-$2/$3"

Request /users/123 → Proxies to https://api.example.com/v1/user/123

routes.fly

Fly.io-specific routing configuration.

routes.fly.sticky_sessions

Cookie-based session affinity for routing requests to the same machine.

Field Type Default Description
enabled boolean false Enable sticky sessions
cookie_name string "_navigator" Name of the session cookie
cookie_max_age string "24h" Cookie lifetime (duration format)
cookie_secure boolean true Set Secure flag (HTTPS only)
cookie_httponly boolean true Set HttpOnly flag
cookie_samesite string "Lax" SameSite attribute: "Strict", "Lax", "None"
cookie_path string "/" Cookie path
paths array [] Specific URL patterns for sticky sessions

routes.fly.replay

Fly-Replay routing for region/machine targeting.

Field Type Required Description
path string URL regex pattern
region string Target Fly.io region
app string Target Fly.io app name
machine string Target machine ID (requires app)
status integer HTTP status code
methods array HTTP methods to match

hooks

Lifecycle hooks for server and tenant events.

hooks:
  # Server lifecycle hooks
  server:
    start:                        # Execute when Navigator starts
      - command: /usr/local/bin/init.sh
        args: ["--setup"]
        timeout: 30s
    ready:                        # Execute when Navigator is ready
      - command: curl
        args: ["-X", "POST", "http://monitoring.example.com/ready"]
        timeout: 5s
    idle:                         # Execute before machine suspend/stop
      - command: /usr/local/bin/backup-to-s3.sh
        timeout: 5m
    resume:                       # Execute after machine resume
      - command: /usr/local/bin/restore-from-s3.sh
        timeout: 2m

  # Default tenant lifecycle hooks (executed for all tenants)
  tenant:
    start:                        # Execute after tenant app starts
      - command: /usr/local/bin/tenant-init.sh
        args: ["${database}"]
        timeout: 30s
    stop:                         # Execute before tenant app stops
      - command: /usr/local/bin/tenant-backup.sh
        args: ["${database}"]
        timeout: 2m

hooks.server

Server-level lifecycle hooks execute at key Navigator events.

Event When Executed Use Cases
start Before Navigator accepts requests Initialize services, run migrations
ready After Navigator starts listening Notify monitoring, warm caches
idle Before machine suspend/stop (Fly.io) Upload data to S3, checkpoint state
resume After machine resume (Fly.io) Download data from S3, reconnect services

hooks.tenant

Default tenant hooks execute for all tenant applications. Can be overridden per-tenant under applications.tenants[].hooks.

Event When Executed Use Cases
start After tenant app starts Initialize tenant data, warm caches
stop Before tenant app stops Backup database, sync to storage

Hook Configuration

Each hook entry supports:

Field Type Required Description
command string Command to execute
args array Command arguments (supports ${var} substitution)
timeout string Max execution time (duration: "30s", "5m")

Environment: - Server hooks receive Navigator's environment - Tenant hooks receive tenant's full environment (including env and var values)

Execution Order: - Multiple hooks execute sequentially in order - Failed hooks log errors but don't stop Navigator - Tenant stop: default hooks → tenant-specific hooks

logging

Logging configuration for Navigator and managed processes.

logging:
  format: json                    # "text" or "json"
  file: "/var/log/navigator.log" # Optional file output

  # Vector integration (optional)
  vector:
    enabled: true
    socket: "/tmp/vector.sock"
    config: "/etc/vector/vector.toml"
Field Type Default Description
format string "text" Log format: "text" or "json"
file string "" Optional file path for log output (supports {{app}} template)

logging.vector

Professional log aggregation with automatic Vector process management.

Field Type Default Description
enabled boolean false Enable Vector integration
socket string "" Unix socket path for Vector communication
config string "" Path to vector.toml configuration file

Log Format: All process output (Navigator, web apps, managed processes) includes: - Text mode: [source.stream] prefix (e.g., [2025/boston.stdout]) - JSON mode: Structured with timestamp, source, stream, message, tenant fields

Environment Variable Substitution

Navigator supports environment variable substitution using ${VAR} syntax:

applications:
  global_env:
    # Simple substitution
    DATABASE_URL: "${DATABASE_URL}"

    # With default value
    REDIS_URL: "${REDIS_URL:-redis://localhost:6379}"

    # Nested in strings
    LOG_LEVEL: "Rails ${RAILS_ENV} logging"

Template Variables

Use template variables for multi-tenant configurations:

applications:
  env:
    DATABASE_NAME: "${database}"
    TENANT_ID: "${tenant_id}"

  tenants:
    - name: customer1
      var:
        database: "app_customer1"
        tenant_id: "cust1"

Validation Rules

Navigator validates configuration on startup:

  1. Required fields: server.listen, applications.tenants[].path
  2. Port ranges: Listen port must be 1-65535
  3. Path format: Tenant paths must start and end with /
  4. File paths: Must be accessible by Navigator process (htpasswd, config files, maintenance page)
  5. Regex patterns: Must compile successfully (routes.rewrites, routes.fly.replay)
  6. Process names: Must be unique within managed_processes
  7. Duration format: Must be valid Go duration (e.g., "30s", "5m", "1h30m")
  8. Hook timeouts: Should be reasonable (<10m for most operations)

Examples

Basic Single App

server:
  listen: 3000

applications:
  tenants:
    - name: app
      path: /
      working_dir: /var/www/app

Multi-Tenant with Auth

server:
  listen: 3000
  static:
    public_dir: /var/www/public
    allowed_extensions: [html, css, js, png, jpg, gif]
    cache_control:
      default: "1h"
      overrides:
        - path: /assets/
          max_age: 24h

auth:
  enabled: true
  realm: "Restricted"
  htpasswd: /etc/navigator/htpasswd
  public_paths: ["/assets/", "*.css", "*.js"]

maintenance:
  page: "/503.html"

applications:
  pools:
    max_size: 10
    timeout: 5m
    start_port: 4000

  env:
    DATABASE_URL: "sqlite3://db/${database}.sqlite3"

  tenants:
    - path: /tenant1/
      var:
        database: tenant1
    - path: /tenant2/
      var:
        database: tenant2

With Background Jobs and Hooks

server:
  listen: 3000
  idle:
    action: suspend
    timeout: 20m

hooks:
  server:
    idle:
      - command: /usr/local/bin/backup-all.sh
        timeout: 5m
  tenant:
    stop:
      - command: /usr/local/bin/backup-tenant.sh
        args: ["${database}"]
        timeout: 2m

managed_processes:
  - name: redis
    command: redis-server
    args: ["/etc/redis/redis.conf"]
    auto_restart: true

  - name: sidekiq
    command: bundle
    args: [exec, sidekiq]
    working_dir: /app
    env:
      REDIS_URL: redis://localhost:6379
    auto_restart: true
    start_delay: 2s

applications:
  pools:
    max_size: 5
    timeout: 10m
    start_port: 4000

  env:
    REDIS_URL: redis://localhost:6379
    DATABASE_URL: "sqlite3://db/${database}.sqlite3"

  tenants:
    - path: /
      var:
        database: production

Complete Fly.io Configuration

server:
  listen: 3000
  static:
    public_dir: /var/www/public
    allowed_extensions: [html, css, js, png, jpg, svg, ico, woff2]
    try_files: [index.html, .html]
    cache_control:
      default: "1h"
      overrides:
        - path: /assets/
          max_age: 7d
  idle:
    action: suspend
    timeout: 20m

auth:
  enabled: true
  realm: "Protected Application"
  htpasswd: /etc/navigator/htpasswd
  public_paths:
    - "/assets/*"
    - "*.css"
    - "*.js"
    - "/favicon.ico"

maintenance:
  page: "/503.html"

routes:
  rewrites:
    - pattern: "^/old-path/(.*)"
      replacement: "/new-path/$1"
      redirect: true
      status: 301

  reverse_proxies:
    - name: api-backend
      path: "^/api/"
      target: "https://api.example.com"
      strip_path: true

  fly:
    sticky_sessions:
      enabled: true
      cookie_name: "_navigator_machine"
      cookie_max_age: "2h"
      cookie_secure: true
      cookie_httponly: true
      cookie_samesite: "Lax"
      paths:
        - "/app/*"
        - "/dashboard/*"

    replay:
      - path: "^/regions/syd/"
        region: syd
        status: 307

applications:
  pools:
    max_size: 10
    timeout: 10m
    start_port: 4000

  health_check: "/up"
  startup_timeout: "5s"

  env:
    DATABASE_URL: "sqlite3://db/${database}.sqlite3"
    RAILS_ENV: production

  tenants:
    - path: /tenant1/
      var:
        database: tenant1
    - path: /tenant2/
      var:
        database: tenant2

managed_processes:
  - name: redis
    command: redis-server
    args: ["/etc/redis/redis.conf"]
    auto_restart: true

hooks:
  server:
    idle:
      - command: /usr/local/bin/backup-to-s3.sh
        timeout: 5m
    resume:
      - command: /usr/local/bin/restore-from-s3.sh
        timeout: 2m

logging:
  format: json
  file: /var/log/navigator.log

See Also