Host Multiple Private Websites and a Hermes Dashboard with Tailscale Serve

A private network connecting one compact server to several website and dashboard cards

Use Tailscale Serve to reach multiple local websites and a password-protected Hermes dashboard, with correct HTTPS ports, Host checks, and troubleshooting. The setup below keeps applications on loopback and gives each one a root URL on a different HTTPS port.

This guide was checked on 2026-10-26 against official documentation, the local Tailscale 1.104.2 CLI help, and Hermes dashboard source at commit 509cc4202678db815a2addbc7983515643d59e2e. Installed Hermes packages can lag behind that source. The commands are instructions for your host; writing this article did not change your tailnet, expose services, or test a live remote login.

1. What the original proposal gets right—and what needs changing

Tailscale Serve can reverse-proxy local applications to devices allowed to reach them in your tailnet. Hermes Agent has a browser administration dashboard, including configuration, credentials, sessions, and other sensitive controls. It needs application authentication as well as network access control.

Original claimCorrected guidance
A Tailscale hostname can trigger Invalid Host headerCorrect for a request whose forwarded hostname Hermes does not trust; confirm which backend returns the error
Set HERMES_DASHBOARD_PUBLIC_URLCorrect for versions implementing it, but a non-loopback public hostname also engages the authentication gate
Use 0.0.0.0 --insecure as a safe workaroundRemove this recommendation. All-interface binding can expose the service on LAN interfaces; current Hermes treats --insecure as deprecated and ineffective
Use one HTTPS port per siteA useful simple approach; public and backend port numbers need not match
Reset Serve before adding sitesUnnecessary by default; reset removes the device’s existing Serve configuration
Subpaths always break applicationsToo broad. They work when base paths, redirects, assets, and WebSockets are configured consistently

These corrections follow the Hermes dashboard documentation, its Host/auth boundary implementation, and the Serve command reference.

2. The layout: one machine, several local applications

Use your real machine’s full MagicDNS name wherever this example says your-machine.tail1234.ts.net.

ApplicationLocal HTTP backendBrowser URL through Serve
Website A, for example an existing Vite projecthttp://127.0.0.1:5173https://your-machine.tail1234.ts.net/
Hermes dashboardhttp://127.0.0.1:9199https://your-machine.tail1234.ts.net:9199/
Optional website Bhttp://127.0.0.1:4321https://your-machine.tail1234.ts.net:8443/

Serve terminates HTTPS on the Tailscale side and proxies to the selected loopback HTTP service. The Hermes listener and Serve can both use the number 9199 because they listen on different addresses. Alternatively, map external 8443 to backend 9199; then Hermes’s configured URL must contain :8443.

Serve is for private tailnet access. Tailscale Funnel publishes to the internet and is a different feature. This tutorial uses Serve, not Funnel.

3. Prepare the host, client, and HTTPS name

Install Tailscale on the hosting machine and on the device you will use to open the sites. Sign in and confirm the host is online. Find its full DNS name in the device details; do not guess the tail…ts.net suffix. Check MagicDNS and HTTPS certificate setup.

On the hosting machine, inspect the tools and existing rules:

tailscale version
tailscale serve --help
tailscale serve status
hermes --version
hermes dashboard --help

If Serve asks you to enable HTTPS through a setup URL, complete the official flow. Review any optional Funnel-related settings separately. On macOS, ensure the tailscale CLI belongs to the installed app variant. The Serve reference notes that App Store and standalone system-extension variants can share ports but not files/directories; this tutorial shares ports.

Tailscale's official Start using Serve consent screen

Official Tailscale documentation image of the Serve setup flow; it is not a capture of the reader’s tailnet. (Source: Serve overview, checked 2026-10-26)

Before adding a listener, inspect whether its port is already configured. Keep a private copy of the status if you need a rollback reference. Do not begin with tailscale serve reset: other sites on the device can depend on those rules.

4. Make website A work locally first

Keep its server listening on 127.0.0.1:5173 and check it before adding a proxy:

curl -I http://127.0.0.1:5173/

For an existing Vite project, merge the following server options into its existing vite.config.js or vite.config.ts; retain its plugins and other settings:

import { defineConfig } from 'vite';

export default defineConfig({
  server: {
    host: '127.0.0.1',
    port: 5173,
    strictPort: true,
    allowedHosts: ['your-machine.tail1234.ts.net'],
  },
});

allowedHosts contains a hostname, without a scheme, port, or path. strictPort avoids silently switching to another backend port. Do not use allowedHosts: true to bypass a Host error. Vite’s explicit allowlist exists to protect the development server from DNS rebinding. (Vite server options)

This is a private development-server example. Long-term hosting should use the application’s supported production build/server rather than assuming a dev server is a production service.

5. Start Hermes with an explicit URL and authentication

Hermes’s documented default port is 9119. This walkthrough deliberately uses 9199; they are not interchangeable unless every backend reference is updated. First check that your installed version supports the settings in the current dashboard guide.

For a temporary test in macOS’s zsh, open a dedicated terminal and run the following. The password prompt avoids putting the password literal into the command history:

export HERMES_DASHBOARD_PUBLIC_URL="https://your-machine.tail1234.ts.net:9199"
export HERMES_DASHBOARD_BASIC_AUTH_USERNAME="admin"
read -rs "HERMES_DASHBOARD_BASIC_AUTH_PASSWORD?Dashboard password: "
printf '\n'
export HERMES_DASHBOARD_BASIC_AUTH_PASSWORD
hermes dashboard --host 127.0.0.1 --port 9199 --no-open

Use a strong unique password and leave this process running. The password remains in this process environment; this is not persistent secret storage. For a long-lived setup, follow the official username/password provider instructions for a password hash and stable session-signing secret, stored outside the website repository. Without a stable secret, restarting the dashboard invalidates sessions. The built-in shared-password provider is intended for trusted networks/VPN use; this is not a public-internet deployment recipe.

Hermes official administration interface with configuration sections and editable settings

Official Hermes documentation image of the configuration dashboard. It illustrates the administration surface being protected, not a successful login on the reader’s machine; UI details vary by release. (Source: Hermes dashboard guide, checked 2026-10-26)

In another terminal, check the status with the browser-facing Host value:

curl -sS -H 'Host: your-machine.tail1234.ts.net:9199' \
  http://127.0.0.1:9199/api/status

For the password configuration, expect auth_required: true and an available basic provider. A successful public status response is a health check, not proof that protected APIs or WebSockets are accessible. If Hermes refuses to start because authentication is unconfigured, fix the provider rather than weakening the bind address.

Hermes documentation stating that a non-loopback public_url engages authentication even on loopback and fails closed without a provider

Captured public official documentation: declaring the external URL also changes the authentication requirement. (Source: Public URL override, captured 2026-10-26)

6. Why this resolves Invalid Host header

The backend’s bind address and the HTTP Host are different things. Serve connects to 127.0.0.1, but its HTTP proxy normally preserves the browser’s Host. Hermes’s DNS-rebinding guard sees the Tailscale hostname rather than localhost. The configured public URL adds that exact hostname to the trusted set; it does not disable the guard for arbitrary hosts. (Tailscale proxy implementation, Hermes boundary implementation)

Use the full URL, including https:// and the external port. In the checked Hermes source, Host validation extracts the hostname; the complete URL also matters for redirects and the browser-facing application URL. Do not invent an --allowed-hosts option: it is absent from the installed CLI help checked for this article. Version-specific options must be confirmed in that version’s help.

Avoid the proposed --host 0.0.0.0 --insecure workaround. All-interface binding broadens network exposure, and current Hermes does not let --insecure bypass authentication. A private tailnet does not automatically restrict a service also listening on the LAN.

7. Add the HTTPS listeners independently

With both backends healthy and the chosen external ports available, run these on the same host:

tailscale serve --bg --https=443 http://127.0.0.1:5173
tailscale serve --bg --https=9199 http://127.0.0.1:9199
tailscale serve status

The two commands configure different HTTPS listeners; the second does not need to replace the first. An optional third site can use:

tailscale serve --bg --https=8443 http://127.0.0.1:4321

The route information should correspond to the following illustrative layout; actual status formatting varies:

https://your-machine.tail1234.ts.net/
  -> http://127.0.0.1:5173

https://your-machine.tail1234.ts.net:9199/
  -> http://127.0.0.1:9199

Tailscale manages HTTPS certificates for the configured tailnet DNS name when HTTPS is enabled. --bg persists the Serve configuration; it does not start or supervise Vite or Hermes. Those processes and the hosting machine must stay available. (Serve CLI reference)

8. Verify from a second device

On a device connected to Tailscale and permitted by the tailnet policy:

  1. Open website A at the root HTTPS URL. Check its assets and navigation.
  2. Open Hermes at the URL with :9199. Confirm a login is required in a fresh private browser window, then sign in.
  3. Open a session or chat to verify the WebSocket-backed features, not only the initial HTML.
  4. If using Vite, check that an edit refreshes the page. Hardcoded HMR host/port settings can still point to localhost and need correcting.

Review the host’s Tailscale access policy. Permit only the intended users/devices to reach the Serve listener ports, here TCP 443 and 9199, adding 8443 only if used. Existing policies can be broader than expected. Network permission and Hermes login are separate checks; Serve’s identity headers do not automatically become a Hermes login.

9. Ports, paths, and hostname isolation

Separate ports are convenient because each application keeps / as its root. Subpaths such as /hermes can work when the application and proxy agree on asset bases, redirects, cookies, and WebSocket paths. Current Hermes documents prefix support; “every dashboard breaks under a subpath” is inaccurate.

However, different ports are not complete application isolation. They are different browser origins, but cookies are not isolated by port. Avoid placing an untrusted website beside a sensitive dashboard on the same hostname. If you need clearer separation, consider distinct hosting nodes or Tailscale Services, with its own setup and access policy. (Cookie port limitation: RFC 6265)

10. Diagnose and remove only the affected listener

SymptomFirst check
Invalid Host header / blocked hostWhich backend produced it; correct Hermes public URL or Vite hostname allowlist
Hermes refuses to startA non-loopback public URL requires a configured authentication provider
401 or login loopCredentials, auth provider, session cookies, external HTTPS URL and proxy headers
502 from ServeBackend process, correct loopback address and port, local curl test
HTML loads but chat failsWebSocket authentication and Host/Origin validation; status alone is insufficient
Certificate or DNS errorFull actual ts.net name, HTTPS setup, client Tailscale connection and access policy
Wrong site appearsInspect Serve status and whether a dev server switched ports

To stop only Hermes’s Serve listener:

tailscale serve --https=9199 off
tailscale serve status

This removes that listener without shutting down the Hermes backend or deliberately removing the 443 site. Use tailscale serve reset only when you intend to clear the device’s entire Serve setup. For unattended operation, configure the application’s supported service manager with the same URL and authentication settings; the temporary export commands above do not configure an existing daemon.