Dynamic Applications and Reverse Proxies

July 16, 2026 ยท View on GitHub

A dynamic application creates responses using code, data, authentication, or user input. It requires more operational controls than a static site.

A Common Architecture

Browser
  |
  | HTTPS :443
  v
Nginx reverse proxy
  |
  | HTTP on 127.0.0.1:3000
  v
Application process
  |
  v
Database or external services

The reverse proxy handles the public connection and forwards selected requests to an application bound to the local interface.

Why Use a Reverse Proxy

A reverse proxy can:

  • Terminate TLS.
  • Route multiple hostnames or paths.
  • Apply request size and timeout limits.
  • Serve static files efficiently.
  • Add standard headers.
  • Keep the application port off the public network.

It does not automatically make an insecure application safe.

Bind the Application Locally

Prefer:

127.0.0.1:3000

over:

0.0.0.0:3000

when only the reverse proxy should reach the application. Confirm the real listener:

sudo ss -lntp

Example Nginx Location

location / {
    proxy_pass http://127.0.0.1:3000;
    proxy_http_version 1.1;

    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;

    proxy_connect_timeout 5s;
    proxy_read_timeout 30s;
}

Header trust is an application decision. Configure the application to trust proxy headers only from the actual proxy, or clients may forge scheme and address information.

Application Process Management

A production application needs:

  • A non-root service account
  • A defined working directory
  • Environment or secret loading
  • Automatic restart with limits
  • Structured logs
  • Health checks
  • Graceful shutdown
  • A deployment and rollback procedure

Do not use a development server as a permanent production process unless its documentation explicitly supports production operation.

Secrets and Configuration

Separate configuration into:

Public configuration: ports, feature flags, public URLs
Sensitive configuration: database passwords, signing keys, API tokens

The frontend bundle is public. A secret embedded in browser JavaScript is no longer secret.

Database Safety

If the application uses a database:

  • Bind it to a private interface unless remote access is required.
  • Use a dedicated database role with limited permissions.
  • Apply schema migrations through a reviewed process.
  • Back up data before migrations.
  • Test restoration, not only backup creation.
  • Avoid logging passwords, tokens, or full personal records.

Uploads and User Input

Treat all user input as untrusted.

  • Validate type, length, and expected format on the server.
  • Generate storage names instead of trusting uploaded filenames.
  • Store uploads outside executable application directories.
  • Set size limits.
  • Escape output according to its HTML, URL, JavaScript, shell, or SQL context.
  • Use parameterized database queries.

Client-side validation improves usability but is not a security boundary.

Health Checks

Create a lightweight endpoint that proves the application can serve requests without exposing secrets:

GET /healthz -> 200 OK

Decide whether a health check verifies only the process or also dependencies such as the database. Deep checks can create load or expose dependency failures to the public.

Diagnose a 502 Response

  1. Confirm the application process is running.
  2. Confirm the expected local port is listening.
  3. Request the application directly from the server.
  4. Check reverse-proxy error logs.
  5. Check application logs.
  6. Verify protocol, address, and port in proxy_pass.
  7. Check local firewall or security policy.

Example:

curl -I http://127.0.0.1:3000/healthz
sudo tail -n 100 /var/log/nginx/example.dpdns.org.error.log

Deployment Exercise

Draw a dynamic application architecture and label:

  • Public ports
  • Private ports
  • TLS termination
  • Secret storage
  • Logs
  • Backup boundary
  • Health check
  • Rollback target

Do not deploy it until every label has an owner.

Continue to Accessibility and Search Basics.