Horizon

Expose a site behind local NGINX

Expose a site served by local NGINX through a Horizon tunnel, including a server_name virtual host such as mysite.test.

Share a site that NGINX serves on your machine, with an HTTPS URL. You tunnel to NGINX, not to the app behind it.

Before you begin

  • Node.js 18 or later
  • A Horizon account and the CLI (see Getting started)
  • A reserved subdomain for -s. Reserve one on the Subdomains page.
  • NGINX running locally. The examples use port 8080.

Check which Host NGINX expects

NGINX tests only the Host header to pick a server. If the value matches no server_name, NGINX uses the default server for that port. The Horizon CLI sets Host to the host of the URL you give it, so the URL decides which server answers.

This site answers to mysite.test:

nginx.conf
server {
    listen      8080;
    server_name mysite.test;
    root        /var/www/mysite;
}

Start a tunnel to NGINX's port

If you have one site on the port, or the default server is the one you want, tunnel to localhost:

hrzn tunnel http://localhost:8080 -s my-app

If a server_name picks your site, tunnel to that hostname. It must resolve on your machine, for example through an entry in /etc/hosts.

hrzn tunnel http://mysite.test:8080 -s my-app

Horizon then sends Host: mysite.test:8080. NGINX matches mysite.test.

Output
HORIZON: Tunnel connected
  URL          https://my-app.hrzn.run (reserved)
  Forwarding   http://mysite.test:8080
  Request log  https://hrzn.run/dashboard/tunnels/my-app

Use -s with a subdomain you reserved. Reserved subdomains are a paid feature, see Pricing. Keep this terminal open.

Open the URL

Open https://my-app.hrzn.run. A first-time browser visitor sees the Before you continue page. Select Continue to site.

Check it works

The Horizon terminal prints a line per request:

Output
  GET     200  /

The public host arrives at NGINX in X-Forwarded-Host. If NGINX proxies to an app behind it, pass the header on with proxy_set_header. NGINX's default for Host in a proxied request is $proxy_host, so set the headers you need:

nginx.conf
location / {
    proxy_pass       http://localhost:8000;
    proxy_set_header Host      $host;
    proxy_set_header X-Forwarded-Host $http_x_forwarded_host;
}

Troubleshooting

The wrong site answers

NGINX routed the request to the default server for the port because Host matched no server_name. Tunnel to the hostname in server_name, as in hrzn tunnel http://mysite.test:8080.

The hostname doesn't resolve

The Horizon CLI connects from your machine, so mysite.test must resolve there. Add it to /etc/hosts, then run the tunnel command again.

502 Bad Gateway

NGINX can't reach the app behind proxy_pass. Check that the app listens on the address in proxy_pass. The tunnel is working, because the response comes from NGINX.

The URL changed after a restart

You started the tunnel without -s, so Horizon gave you a new random subdomain. Restart with -s my-app.

Next steps

On this page