Skip to content
GitHubLinkedIn

NGINX vhost templates

Use this page to create new NGINX vhosts on web.core.lef (and to keep a second ingress proxy consistent).

These templates are derived from the current listener-separated setup (see NGINX ingress (public vs internal)).

  • Public vhosts bind only to the ingress VIPs (192.168.20.112–192.168.20.120).
  • VPN-only vhosts (public DNS, VPN-only) bind only to 192.168.20.111.
  • Internal vhosts bind only to 192.168.20.2.
  • Enabled vhosts live under:
    • /etc/nginx/sites-enabled/public/
    • /etc/nginx/sites-enabled/vpn/
    • /etc/nginx/sites-enabled/internal/
  • Canonical upstream groups live under /etc/nginx/upstreams-enabled/.
  • Host allow/deny rules are vhost-specific (use for internal-only services).
  • Known-domain map (public :80 redirects + internal :80 behavior): add <listener-ip>:<hostname> 1; to /etc/nginx/maps/known_domains.map (or regenerate via generate-known-domains-map.sh).
  • Upstream definition: add/update the relevant upstream in /etc/nginx/upstreams-enabled/<zone>.conf.
  • Certificates
    • Public: ensure the ACME/Let’s Encrypt cert exists for the hostname (paths typically under /etc/letsencrypt/live/<hostname>/).
    • Internal: use the appropriate wildcard cert/key under /etc/nginx/ssl/ when possible.

Reverse proxy vhosts (normalize to one shape)

Section titled “Reverse proxy vhosts (normalize to one shape)”

Most vhosts on web.core.lef are reverse proxies. Keep them consistent by default:

  • Always include security-headers.conf.
  • Prefer the shared snippets over inline directive duplication:
    • proxy-base.conf
    • proxy-websockets.conf (when needed)
    • proxy-nocache.conf (when appropriate)
    • proxy-rate-limit.conf / proxy-retry-fallback.conf (optional hardening)
  • Use a dedicated “restricted” vhost shape when a public hostname must be VPN/LAN-only (enforced via allow/deny).
  • Use a “streaming” vhost shape when buffering breaks the app (proxy_buffering off).

VPN-only (public DNS) vhosts use the same shapes as public vhosts, but:

  • bind to the VPN-only VIP (192.168.20.111)
  • live under /etc/nginx/sites-enabled/vpn/

For Thinkwise GUI vhosts (static GUI + Indicium API), keep the server blocks structurally identical across hostnames:

  • Use the same static asset caching block.
  • Use a single API prefix pattern (default: /api/) and prefer proxy_pass http://<upstream>/; to strip the prefix.
  • Keep cache-control: no-store for API responses.

If a vhost uses a different API prefix (example: /indicium/), update only the API location block and keep everything else consistent.

This grouping is based on the current listener-separated config on web.core.lef:

  • Thinkwise GUI + API (public): concepts.lef.software, credit-hub.lef.software, experience.lef.software, sapore.lef.software, tokiocred.lef.software, unimed.lef.software
    • Exception: my.lef.digital uses a different API prefix (/indicium/) but should follow the same overall shape.
  • Reverse proxy (public; standard): collab.lef.digital, wf.lef.digital, wiki.lef.digital, analytics.coragem.app, proxy.coragem.app, s3.coragem.app, vault.lef.digital
  • Reverse proxy (public; restricted): registry.coragem.app
  • Reverse proxy (public; streaming): report.coragem.app
  • Internal Thinkwise GUI + API: pivot.dev.lef, solution.dev.lef, tokio.dev.lef, trainee.core.lef
  • Internal reverse proxy: ca.app.lef, draw.app.lef, s3.app.lef, uptime.app.lef
    • Exception: ca.app.lef proxies to an HTTPS upstream and disables upstream TLS verification.
  • Internal reverse proxy (streaming): report.app.lef
  • Exceptions (path routing): hook.lef.software, io-trg.lef.digital
  • VPN-only vhosts: see the sites-enabled/vpn/ listener VIP (192.168.20.111) and keep the list in sync with the firewall inventory.
public-proxy-vhost.conf
# Public HTTPS reverse proxy (template)
#
# - Binds ONLY to the intended public VIP.
# - Requires a matching entry in `maps/known_domains.map` for `<vip-ip>:<hostname>`.
#
# Replace placeholders:
# - <vip-ip>             (example: 192.168.20.112)
# - <hostname>           (example: wiki.lef.digital)
# - <access-log-name>    (example: wiki-lef-digital)
# - <upstream-name>      (example: wiki_lef_digital)

server {
    listen <vip-ip>:443 ssl http2;
    server_name <hostname>;

    access_log /var/log/nginx/vhosts/<access-log-name>.access main;
    error_log  /var/log/nginx/vhosts/<access-log-name>.error warn;

    include /etc/nginx/snippets/security-headers.conf;

    location / {
        include /etc/nginx/snippets/proxy-base.conf;
        include /etc/nginx/snippets/proxy-websockets.conf;
        include /etc/nginx/snippets/proxy-nocache.conf;

        proxy_pass http://<upstream-name>;
    }

    ssl_certificate     /etc/letsencrypt/live/<hostname>/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/<hostname>/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
}

On the server:

sudo nginx -t
sudo systemctl reload nginx
sudo ss -lntp | grep nginx

Then validate by targeting the specific listener IP (VIP or internal IP) and host header:

curl -I http://<listener-ip>/ -H 'Host: <hostname>'
curl -Ik https://<hostname>/ --resolve <hostname>:443:<listener-ip>