Route subdomains to different ports on one server

Configure Apache or Nginx to proxy each hostname to a different backend port, including forwarding headers, WebSockets, DNS, and TLS.

Best practices
Illustration of application infrastructure and operations

Use a reverse proxy to route requests by hostname. For example, app.example.com and api.example.com can both accept traffic on standard HTTP/HTTPS ports while forwarding internally to ports 8080 and 3000.

This is proxying, not a browser redirect to a port-bearing URL. DNS points the names at the proxy; the proxy chooses the backend.

Configure Apache virtual hosts

On Debian/Ubuntu installations using a2enmod and the apache2 service, enable the HTTP proxy modules:

sudo a2enmod proxy proxy_http
sudo systemctl restart apache2

Create a virtual host for each name:

<VirtualHost *:80>
    ServerName app.example.com
    ProxyPass / http://127.0.0.1:8080/
    ProxyPassReverse / http://127.0.0.1:8080/
</VirtualHost>

<VirtualHost *:80>
    ServerName api.example.com
    ProxyPass / http://127.0.0.1:3000/
    ProxyPassReverse / http://127.0.0.1:3000/
</VirtualHost>

ProxyPassReverse adjusts certain backend response headers, including redirect locations, so clients continue using the public address. It does not rewrite HTML bodies or every cookie attribute. Configure the application's public base URL and any required cookie rewriting separately.

Use Nginx server blocks instead

The equivalent Nginx pattern is:

server {
    listen 80;
    server_name app.example.com;
    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

Add another server block with server_name api.example.com and a target of http://127.0.0.1:3000 for the API.

Forwarded headers describe the request as the proxy sees it. The application must trust those headers only from known proxies; a client-supplied X-Forwarded-For value is not inherently trustworthy. If the proxy and backend run in separate containers, loopback may refer to the wrong namespace. Use a reachable service address instead.

Handle WebSocket upgrades

For a WebSocket location, explicitly forward the upgrade headers:

# Inside the relevant location block:
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";

For a location serving both ordinary HTTP and WebSocket traffic, consider the conditional map pattern in Nginx's WebSocket documentation so the Connection header matches the request.

Complete DNS, TLS, and backend access controls

  1. Point each hostname at the proxy with appropriate A/AAAA or CNAME records. DNS records do not select the HTTP backend port.
  2. Add certificates covering the hostnames, either individually or through an appropriate wildcard/SAN certificate. Certbot can automate issuance and, with a supported installer plugin, update the server configuration.
  3. When proxy and backend share the host network namespace, bind private backend listeners to loopback where practical. For separate containers or hosts, use reachable private addresses and network access controls instead.

The snippets above demonstrate HTTP routing. Production HTTPS requires the corresponding TLS listener and certificate configuration. Validate the server configuration before reloading, then request every hostname and check that the intended backend responds. Include hostname and upstream information in access logs to catch requests falling into the default site.

Frequently asked questions

Why not expose subdomain:port directly?

A proxy gives users ordinary URLs, centralizes TLS and routing, and avoids dependence on nonstandard ports that some client networks block. Direct access can still be appropriate in controlled environments.

Why do I get 503 after configuring the proxy?

A 503 may originate at the proxy or backend. Read the error log before attributing it to a refused connection. Check that the backend is running and listening, for example with ss -tlnp | grep 8080. On SELinux systems, examine denial records and adjust only the policy needed for the confirmed access path.

Does every subdomain need a separate backend port?

No. Several names can route to one application. A multi-tenant application may use the Host header to select a tenant, provided it validates accepted hostnames and tenant access.

References

Related reading

Get a tailored plan

Contact us

Join the community

Scan with WeChat
to join the community

Try Guance

Start online and pay only for what you use.

Get started

Choose a Guance plan

Code hosting