Skip to content

Plan: Split nginx.conf into modular config files

Status: DONE

Current Phase: All phases complete

Last Updated: 2026-03-17 00:01


Problem

nginx/nginx.conf is 830 lines — a single monolith containing shared http settings, CORS maps, upstream definitions, rate-limiting, logging, gzip, and both the production and development server blocks. Any change to one environment risks breaking the other, and the file far exceeds the 200-line limit.

Goal

Split into 3 files:

File Contents ~Lines
nginx/nginx.conf events, http block with shared settings (maps, upstreams, rate-limits, logging, gzip, HTTP→HTTPS redirect), include conf.d/*.conf; ~140
nginx/conf.d/prod.conf Production HTTPS server block (api.indoxhub.com on 443) ~380
nginx/conf.d/dev.conf Development HTTPS server block (dev-api.indoxhub.com on 443) ~310

What stays where

Stays in nginx/nginx.conf (shared)

  • events {} block (lines 4–8)
  • client_max_body_size, keepalive_* (lines 12–16)
  • All map blocks: $indoxrouter_prod_origin, $indoxrouter_dev_origin, $dev_localhost_origin, $indoxrouter_dev_combined, $service_name (lines 21–119)
  • Both upstream blocks: indoxrouter_backend, indoxrouter_backend_dev (lines 65–77)
  • resolver (line 80)
  • limit_req_zone directives (lines 83–84)
  • log_format, access_log, error_log (lines 87–95)
  • gzip block (lines 98–112)
  • HTTP→HTTPS redirect server {} on port 80 (lines 122–137) — serves both domains
  • New directive: include /etc/nginx/conf.d/*.conf;

Moves to nginx/conf.d/prod.conf

  • The entire server { listen 443 ssl; server_name api.indoxhub.com; ... } block (lines 140–519)
  • Includes: SSL config, security headers, /health, /status, /static/, /mobile-auth-test, /admin/ (IP-restricted), /v1/, /api/v1/, catch-all /
  • All @indoxrouter_* named locations (down, api_down, admin_down, static_fallback)

Moves to nginx/conf.d/dev.conf

  • The entire server { listen 443 ssl; server_name dev-api.indoxhub.com; ... } block (lines 522–828)
  • Includes: SSL config, security headers, /health, /status, /static/, /v1/, /api/v1/, catch-all /
  • All @indoxrouter_dev_* named locations
  • Note: dev has no /admin/ block or /mobile-auth-test — key structural difference from prod

Files affected

Action File Change
Rewrite nginx/nginx.conf Remove both 443 server blocks, add include
Create nginx/conf.d/prod.conf Prod HTTPS server block
Create nginx/conf.d/dev.conf Dev HTTPS server block
Modify nginx/Dockerfile Add RUN rm -f /etc/nginx/conf.d/default.conf + COPY conf.d/ /etc/nginx/conf.d/
Modify nginx/docker-compose.yml Add volume ./conf.d/:/etc/nginx/conf.d/:ro
Modify .github/workflows/NGINX_deploy.yml Copy conf.d/ alongside nginx.conf

Not touched

  • docker/local/nginx-local.conf — separate local config, unrelated
  • DEV_restart_nginx.yml, PROD_restart_nginx.yml — restart-only workflows, no file copy

Phases

  • [x] Phase 1: Create nginx/conf.d/prod.conf and nginx/conf.d/dev.conf
  • Extract prod server block (lines 140–519) → nginx/conf.d/prod.conf
  • Extract dev server block (lines 522–828) → nginx/conf.d/dev.conf
  • Each file is a standalone server { ... } block (no wrapping http { })
  • Original nginx.conf untouched in this phase

  • [x] Phase 2: Rewrite nginx/nginx.conf as shared-only

  • Keep: events, http shell, maps, upstreams, resolver, rate-limit zones, logging, gzip, port-80 redirect server
  • Remove: both 443 server blocks
  • Add: include /etc/nginx/conf.d/*.conf; at the end of the http {} block (before closing })

  • [x] Phase 3: Update Dockerfile, docker-compose.yml, and CI workflow

  • nginx/Dockerfile:
    • Add RUN rm -f /etc/nginx/conf.d/default.conf (remove alpine default)
    • Add COPY conf.d/ /etc/nginx/conf.d/
  • nginx/docker-compose.yml:
    • Add volume: ./conf.d/:/etc/nginx/conf.d/:ro
  • .github/workflows/NGINX_deploy.yml:

    # Before:
    cp ${{ github.workspace }}/nginx/nginx.conf /opt/router-nginx/nginx.conf
    
    # After:
    cp ${{ github.workspace }}/nginx/nginx.conf /opt/router-nginx/nginx.conf
    mkdir -p /opt/router-nginx/conf.d
    cp ${{ github.workspace }}/nginx/conf.d/*.conf /opt/router-nginx/conf.d/
    

  • [x] Phase 4: Validate

  • Verify all files are within line limits
  • Verify no logic changes by comparing the resulting concatenated config with the original
  • nginx -t syntax check (same as CI does)
  • Confirm the workflow trigger path nginx/** already covers conf.d/ changes (it does)

Risks and mitigations

Risk Mitigation
include *.conf loads alphabetically — order could matter nginx server blocks match by server_name, not file order. Safe.
nginx:alpine ships with /etc/nginx/conf.d/default.conf Add RUN rm -f /etc/nginx/conf.d/default.conf in Dockerfile before our COPY
CI deploy must create conf.d/ on the host Add mkdir -p /opt/router-nginx/conf.d in the deploy step
Shared map / upstream blocks referenced by both envs Keep them in nginx.conf — they're http-level directives, visible to all included files

Notes

  • This is a pure structural refactor — zero functional changes to routing, CORS, SSL, or upstream behavior.
  • The HTTP→HTTPS redirect server (port 80) handles both api.indoxhub.com and dev-api.indoxhub.com in one block — stays in the shared file.
  • The docker-compose.yml volume mount for conf.d/ means changes to individual env configs can be hot-tested without rebuilding the image.
Documentation last built on May 23, 2026