Install an agent
The agent is the one piece of Houseguest™ that runs inside your network. It opens one connection out to the edge on port 443 and keeps it open. Nothing connects in, so there are no firewall rules to add. It forwards only the hostnames and TCP services listed on its own host.
What it needs
Section titled “What it needs”- A Linux host (amd64 or arm64) with Docker, or with systemd and no Docker.
- Outbound HTTPS (port 443) to
tunnel.houseguest.dev, or totunnel.<your domain>on a self-hosted edge. - A route to the app’s internal address, and DNS that gives that address for the app’s hostnames (or the address itself; see below). The app must be served over HTTPS with a certificate valid for its hostnames: the agent verifies it, as a browser on your network would. A certificate from your own internal CA works; see Apps with an internal CA.
- The Add agents permission in the console (Owner and Admin have it).
One agent can serve any number of hostnames and previews. Run more than one for separate networks.
Create a join token
Section titled “Create a join token”- In the console, go to Agents and press Add agent.
- Enter:
- Name: usually the host it runs on. Up to 60 characters.
- Hostnames it may forward: exact names such as
app.lab.example.com, or*.lab.example.comso the console can turn names on one at a time. This is the agent’s ceiling: the console can choose within it, never widen it. - Find addresses with internal DNS (ticked by default): the agent looks each hostname up with its host’s DNS when a request arrives and connects to the internal address it finds, on port 443. See Addresses from internal DNS.
- Internal address (only when the box is unticked): one address for every hostname, usually the reverse
proxy in front of the app, such as
10.0.0.5:443.:443is added when no port is given.
- Press Create join token. The next step shows the install command with the token filled in.
The token works once, within 1 hour, and is not shown again. A token nobody used appears on Agents as Waiting for first connection until it expires; its menu has Cancel token.
Run it
Section titled “Run it”Copy the command from the console. It looks like one of these:
docker run -d --name houseguest-agent --restart unless-stopped \ -e HG_JOIN=hgj_… \ -e HG_EDGE=tunnel.houseguest.dev \ -e HG_UPSTREAM=dns \ -e HG_ALLOW='app.lab.example.com,api.lab.example.com' \ -e HG_HOSTNAME="$(hostname)" \ -v houseguest-agent:/data \ ghcr.io/synically-ackward/houseguest-agent:0.4.0Save as compose.yaml and run docker compose up -d in its folder.
services: houseguest-agent: image: ghcr.io/synically-ackward/houseguest-agent:0.4.0 restart: unless-stopped environment: HG_JOIN: hgj_… HG_EDGE: tunnel.houseguest.dev HG_UPSTREAM: 'dns' HG_ALLOW: 'app.lab.example.com,api.lab.example.com' volumes: - houseguest-agent:/datavolumes: houseguest-agent: {}Keep HG_ALLOW quoted: an unquoted *.example.com is a YAML alias.
curl -fsSL https://get.houseguest.dev/install-agent.sh | sudo sh -s -- \ --join hgj_… \ --upstream dns \ --allow 'app.lab.example.com,api.lab.example.com'The installer downloads the agent, checks it against the published SHA-256 sums, and runs it as a locked-down
systemd service, houseguest-agent, under its own unprivileged user. Logs:
journalctl -u houseguest-agent.
With the DNS box unticked, the commands carry the address instead: HG_UPSTREAM=10.0.0.5:443,
--upstream 10.0.0.5:443. The image version in your console may be newer than the one shown here. Use the
console’s command.
Check it connected
Section titled “Check it connected”The Add agent page shows Waiting for connection… and then name connected, with its version, address and hostnames, a few seconds after the agent starts. Agents lists it as Connected.
From the host:
docker logs houseguest-agent # or: journalctl -u houseguest-agentLook for joined as agent N; HG_JOIN is no longer needed and then connected to tunnel.houseguest.dev.
If it does not connect, see Agent troubleshooting.
Keep the volume
Section titled “Keep the volume”On first start the agent makes its own key pair and registers the public key with the join token. The key
lives in the /data volume (/var/lib/houseguest-agent without Docker). Keep it: without it the agent must
join again with a new token. Leaving HG_JOIN set afterwards is harmless; it is ignored once the agent has
joined.
Settings
Section titled “Settings”| Variable | Installer flag | Meaning |
|---|---|---|
HG_JOIN |
--join |
One-time join token. Needed for the first start only |
HG_EDGE |
--edge |
The edge’s tunnel hostname. Default tunnel.houseguest.dev |
HG_UPSTREAM |
--upstream |
For hostnames listed without their own address: dns to find each one’s address with the host’s DNS, or one internal address:port. dns:8443 uses another port. Required by the installer |
HG_ALLOW_PUBLIC_DNS |
1 lets dns use a public address too. Never the edge’s own address |
|
HG_ALLOW |
--allow |
The hostnames the agent forwards: the ceiling. See Allow-lists |
HG_ALLOW_TCP |
--allow-tcp |
TCP services, as exact name:port=address:port entries. See Allow-lists |
HG_UPSTREAM_CA_FILE |
--upstream-ca |
A PEM file with your internal CA, trusted for the app in addition to public roots |
HG_HOSTNAME |
The host’s name, shown in the console’s agent list. Optional | |
HG_EDGE_IP |
Connect to this address instead of looking up HG_EDGE; the certificate is still checked against HG_EDGE. For networks whose DNS answers wrongly for the edge’s name |
|
HG_REMOTE_HOSTS |
off makes the agent ignore hostnames turned on in the console; only exact HG_ALLOW entries are forwarded |
|
HG_STATE |
Where the key is kept. Default /data |
The agent reads these at start. After changing one, recreate the container (or re-run the installer, which rewrites its settings from the flags you give it).
Addresses from internal DNS
Section titled “Addresses from internal DNS”With HG_UPSTREAM=dns (the console’s default from agent 0.3.0), the agent looks up each hostname with its host’s
DNS each time it opens a connection to the app, so an app that moves keeps working. Requirements:
-
The host’s DNS must give the internal address. Inside a company network that is usually already true. A container uses the host’s resolver; to name another, add
--dns 10.0.0.53(dns:in Compose). -
Only internal answers are used:
10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,100.64.0.0/10andfc00::/7. A hostname that resolves to anything else is refused, and the viewer sees why:Houseguest agent: app.lab.example.com resolves to the Houseguest edge (203.0.113.10), so the request would loop back to it; this network's DNS has no internal record for it. Give it an address in HG_ALLOW: app.lab.example.com=ADDRESS:443.The edge case is the common one: without an internal record, the name’s public record points at Houseguest. Add the internal record, or give that one hostname an address in
HG_ALLOW. For an app that really is public,HG_ALLOW_PUBLIC_DNS=1allows public answers (never the edge’s own address). Loopback and link-local answers (such as169.254.169.254) are always refused; write them as an address to mean them. -
TLS is the same: the agent sends the real hostname and checks the certificate against it.
dns and explicit addresses mix: HG_ALLOW='app.lab.example.com,legacy.lab.example.com=10.0.0.9:8443' with
HG_UPSTREAM=dns. The agent logs addresses from this host's DNS for … at start, and the console marks those
hostnames internal DNS. Agents older than 0.3.0 do not understand dns and stop at start with
address "dns" needs host:port.
Apps with an internal CA
Section titled “Apps with an internal CA”If your app’s certificate is signed by your own CA, give the agent that CA:
- Docker: add
-v /path/to/internal-ca.pem:/ca.pem:ro -e HG_UPSTREAM_CA_FILE=/ca.pem. - Installer: add
--upstream-ca /path/to/internal-ca.pem.
The agent logs trusting the internal CA in /ca.pem for the app at start. It is trusted for the app only, in
addition to the public roots. Apps served only over plain HTTP need a TLS reverse proxy in front of them.
Network notes
Section titled “Network notes”- Reaching the app. Docker’s default bridge network reaches the LAN in most setups. If it cannot reach the
internal address, add
--network host(ornetwork_mode: hostin Compose). - Hardening. The image is about 10 MB, has no shell and runs as an unprivileged user. It writes only to
/data, so you can add--read-only(read_only: truein Compose). - Proxies. The agent needs a direct TLS connection to the edge. A proxy that intercepts TLS or strips the
Upgradeheader breaks the tunnel. - Clock. Each connection is signed with the current time. Keep the host’s clock within 2 minutes.
© 2026 Houseguest Technologies LLC. Houseguest™ is its trademark.7723 Tylers Place Blvd., Box #119, West Chester, OH 45069