Trust / Chapter 10

By the end of this chapter you should be able to

  • Explain how two machines behind two routers reach each other without a forwarded port
  • Join your server to a private network and write a policy that denies by default
  • Reach SSH through that policy and keep future dashboards behind SSH forwarding
  • Explain what the coordination provider and relay can observe

Everything you have built works when you are at home. Leave the house and you have nothing: no shell, no dashboards, no photo library.

The traditional answer is to forward a port on your router, and this book is not going to do that. Chapter 12 explains why in detail for the applications your family uses. For administration there is a better answer: put your own devices on a network that only they can join, and let the machinery be reachable there and nowhere else.

How two machines behind two routers find each other #

Tailscale builds a private network out of devices you enrol. Connections begin through an encrypted relay while Tailscale tries to establish a direct path; hard NAT can leave them relayed. The payload remains WireGuard-encrypted end to end in either case.

Underneath is WireGuard, a VPN protocol notable for being small enough to audit. What Tailscale adds is the part WireGuard leaves to you: key distribution, identity, and getting through routers.

Often both devices are behind routers doing network address translation. A device may instead have globally routed IPv6. Tailscale discovers the available paths without requiring you to publish an application listener on either router.

This is NAT traversal. Routers are asymmetric: they block unsolicited inbound traffic and let replies back in for connections started from inside.

Each machine first contacts a coordination server, which observes what public address and port its router assigned and reports that to the other side. Then both send packets at each other's discovered endpoints at the same moment. Each router sees an outbound connection and holds a hole open for its replies, so the packet arriving from the far side fits through.

It does not always work. Some routers assign a different port per destination, which makes the observed endpoint useless, and then traffic falls back to a relay.

A forwarded port compared with a mesh network Forwarding a port opens a listening service to the whole internet and waits for connections, including unwanted ones. A mesh coordinates authenticated devices and attempts a direct encrypted path; when direct traversal fails, a relay may carry the still-encrypted traffic. No service port is forwarded from the home router. Forwarding a port The whole internet Your router, port 443 open Your server, listening Anyone may knock. Most who do are not you. A mesh network Coordination server identity, keys and endpoint discovery Your laptop, behind its router Your server, behind yours authenticated device authenticated device direct or relayed; encrypted The mesh does not require a forwarded application port. A relay can carry ciphertext when a direct path cannot be established; access still depends on identity and policy.
A forwarded port exposes a listener. A mesh authenticates devices, tries a direct encrypted path, and can relay ciphertext when direct traversal fails.
So what does the company actually see?Show me why

Most explanations stop at "it is a mesh, so it is private". Here is the detail.

The coordination server distributes public keys and tells your devices how to find each other. It sees which devices exist, when they are online, and what addresses they connect from. That is real metadata.

It does not hold the private keys, so it cannot decrypt your traffic. When two devices genuinely cannot reach each other directly, traffic falls back to relays run by Tailscale, and that traffic is still encrypted end to end with keys the relay does not have.

The provider knows the network's shape, not its payload contents. If that metadata is not acceptable, Headscale is an open-source coordination-server implementation. The WireGuard data-plane concepts transfer, but the admin UI, DNS, certificate and relay arrangements do not apply unchanged; use Headscale's documentation for those functions.

Join the server #

Install it from Tailscale's repository, pinning the signing key in your playbook exactly as you did for Docker in chapter 6. Then bring it up.

Do not put a reusable credential in the command line. Create a single-use, short-lived, pre-authorised auth key for tag:server, add it temporarily to secrets/tailscale.env, and add this mapping to chapter 9's renderer:

render secrets/tailscale.env '["TAILSCALE_AUTH_KEY"]' tailscale_auth_key

Deploy the encrypted file and updated script, reinstall the script, then render the root-only file. Tailscale accepts an auth key from a file, so neither shell history nor the process arguments contain its value:

$ sudo /usr/local/sbin/render-secrets
$ sudo tailscale up \
    --auth-key=file:/etc/homeserver/secrets/tailscale_auth_key \
    --advertise-tags=tag:server --ssh=false
$ tailscale ip -4

Record the address returned by your tailnet. It is not an internet-routed address.

Tags are the part worth understanding. A device authenticated as you inherits your permissions and follows the account's device-expiry policy. A tagged device is owned by the tag, not by a person: policy is written about tag:server, and tagged-device key expiry is disabled by default. Record any expiry policy you change rather than assuming permanent access.

Auth keys are credentials

An auth key enrols a machine. Anyone holding it can add a device to your network. Make it single-use, short-lived, and pre-authorised for exactly the tag you want.

Once the server has joined, revoke the key in the admin console, remove it from the encrypted file and renderer, delete /etc/homeserver/secrets/tailscale_auth_key, and commit that removal. The enrolled node keeps its own machine key; the one-use enrolment credential is no longer needed.

Deny by default, again #

By default every device on a tailnet can reach every other device on every port. Change it.

Chapter 4 made this argument for the host firewall and it is the same argument. Write a policy in the admin console that names what is allowed:

{
  "groups": {
    "group:admins": ["you@example.com"]
  },
  "tagOwners": {
    "tag:server": ["group:admins"]
  },
  "grants": [
    {
      "src": ["group:admins"],
      "dst": ["tag:server"],
      "ip": ["tcp:22"]
    }
  ]
}

Read it as: people in admins may reach machines tagged server on TCP port 22, and nothing else is granted. Chapter 14 keeps dashboards on server loopback and reaches them through this SSH connection, so their ports do not need tailnet grants.

Nothing here permits a device that is not yours, and nothing permits your server to start connections to your laptop, which matters if the server is compromised. Grants are directional.

Test the policy before you rely on it

The admin console has a preview that evaluates the policy against real devices, and it is worth using rather than assuming. The failure mode of an access policy is silent: everything works because a broader rule you forgot about is permitting it.

Two things to check. That you can reach exactly the ports you listed. And that a device which should not have access genuinely does not, which you test by removing yourself from the group temporarily, or with the policy tester.

Use SSH as the private administration path #

Keep one private entrypoint instead of publishing every dashboard on a tailnet address. Permit SSH on the Tailscale interface:

$ sudo ufw allow in on tailscale0 to any port 22 proto tcp

The bypass from chapter 6 has not gone away

Do not translate this section into Docker ports bound to 100.x.y.z without adding Docker forwarding policy. A published container port still follows FORWARD, bypasses UFW INPUT, and needs the iptables/nftables control discussed in chapter 6.

This book instead leaves administrative web services on 127.0.0.1 and reaches them with ssh -L over the tailnet. The tailnet grant and host SSH rule govern the only inbound administrative listener.

Prove both sides now. From an authorised device away from home, connect to the MagicDNS machine name or tailnet IP with SSH. In the policy tester, require a TCP 443 connection from the same identity to be denied. There is no dashboard yet; chapter 14 supplies the first loopback-only web interfaces and the exact forwarding command.

Use MagicDNS for the machine name #

Turn on MagicDNS and the server receives a name below a Tailscale-managed suffix, such as homeserver.your-tailnet.ts.net. It resolves only on enrolled devices and is convenient for SSH:

$ ssh admin@homeserver.your-tailnet.ts.net

Keep application hostnames on the private CA and domain from chapter 8. A tailscale cert certificate for the machine name would not match photos.example.com, and this chapter does not create a second Traefik route that pretends otherwise.

Tailscale can issue publicly trusted certificates for its machine names through DNS validation. If you adopt that optional design later, treat it as a separate deployment: certificate names are recorded in public Certificate Transparency logs, certificate files need a renewal service and proxy reload, and failures need monitoring. Do not enable it merely to satisfy this chapter.

Keep the machine and application names separate #

MagicDNS now gives the server a stable private name for SSH. It does not replace the LAN application hostname photos.example.com. Chapter 12 creates another exact private HTTPS URL for Immich with Tailscale Serve. Use the URL shown by each tool and require its certificate to match that name.

Split-horizon DNS, where one name has different answers for different clients, is useful in larger designs but is not required here:

Side readingDNS, enough of itRecords, resolvers and caching, and why the same name can honestly give different answers to different people.

I cannot reach my server over the tailnet

Work out whether the problem is the network, the policy, or the service binding

Paste this into a new agent session. It carries everything the agent needs to know about where you are, and asks it to walk you through the problem rather than fix it for you.

I can see my server in the Tailscale admin console but I cannot reach a service on it from another device on my tailnet. I am not sure whether the problem is the connection itself, my access policy, or what address the service is listening on.

My policy allows a port I meant to deny

Find which grant or group is authorising the connection

Paste this into a new agent session. It carries everything the agent needs to know about where you are, and asks it to walk you through the problem rather than fix it for you.

The policy tester says my identity can reach TCP 443 on the tagged server even though the grant in this chapter lists only TCP 22. I want to find the broader grant, group membership or autogroup that is authorising it before I rely on deny by default.

What this replaced #

Administration used to mean being at home or forwarding SSH to the internet. Now it means opening a laptop.

You forwarded no port, set up no dynamic DNS, and exposed no SSH to the internet. This private administrative path is in place before chapter 12 grants the family access to one private application endpoint.

Done when

  • Your server is on the tailnet with a tag, not as your personal identity
  • The enrolment key was supplied from a root-only file, then revoked and removed
  • A policy exists that names what is allowed, and everything else is denied
  • You tested both an allowed SSH connection and a TCP 443 connection that the policy should deny
  • ufw has an explicit rule for the tailscale interface
  • You reached SSH from outside the house without a router port-forward
  • Future dashboards will remain on loopback and use SSH local forwarding

What you picked up

  • A mesh discovers a direct path when it can and relays encrypted traffic when it cannot; neither case requires an application port-forward.
  • The coordination server sees your network's shape, not its contents. Self-host it if that is not acceptable.
  • Tag a server rather than authenticating it as yourself, so policy is about the machine and the key does not expire with your session.
  • Tailnets allow everything between devices by default. Write the policy.
  • Keep one SSH entrypoint on the tailnet and reach loopback dashboards through it instead of publishing each Docker port.
  • MagicDNS gives the machine a private name. Optional public certificates are a separate design with public name logs and renewal work.
  • Keep the SSH machine name, LAN application name and later Serve URL distinct unless you deliberately design split DNS.

Settings

Your values

The book is written with placeholder names so it makes sense to everybody. Put your own in and every chapter, every command and every copy-paste prompt updates to match.

Nothing here is sent anywhere. It is saved in this browser, so it comes back next time. A different browser or a private window gets the placeholders again.

Live preview

$ ssh admin@192.168.1.20
$ sudo ufw allow from 192.168.1.0/24 to any port 22 proto tcp
$ sudo hostnamectl set-hostname homeserver
$ sudo timedatectl set-timezone Europe/Paris

Real commands from chapters 2, 3 and 4. They change as you type.

The account you log in as. Not root, and not necessarily the same name you use on your laptop.

Introduced in Chapter 2, Meet your server

The book's placeholder is admin

What the machine calls itself. You choose it, and it shows up in your shell prompt and your logs.

Introduced in Chapter 3, A safe front door

The book's placeholder is homeserver

The IP address your server has on your home network, from ip -brief addr.

Introduced in Chapter 2, Meet your server

The book's placeholder is 192.168.1.20

The address range and prefix shown by ip route or ip -brief addr, written in CIDR form. Copy the real prefix; do not guess /24.

Introduced in Chapter 2, Meet your server

The book's placeholder is 192.168.1.0/24

The address traffic goes to on its way out of your house, from ip route.

Introduced in Chapter 2, Meet your server

The book's placeholder is 192.168.1.1

In Region/City form, or Etc/UTC if you would rather read logs in UTC.

Introduced in Chapter 3, A safe front door

The book's placeholder is Europe/Paris

A registered name you control. Chapter 8 uses it for the LAN route; chapter 12 uses a separate private Tailscale name remotely.

Introduced in Chapter 8, One door, many rooms

The book's placeholder is example.com

The email identity allowed to administer the tagged server in your Tailscale policy.

Introduced in Chapter 10, Your own private network

The book's placeholder is you@example.com

The mailbox that should receive actionable home-server alerts.

Introduced in Chapter 14, Knowing it is alive

The book's placeholder is alerts@example.com

Once you save, the prose and the commands read with your names, the copy buttons copy your values, and the copy-paste prompts describe your machine accurately. That last one matters: an assistant told your network is 192.168.1.0/24 when it is not will send you chasing the wrong thing.

Anything you leave empty keeps the book's placeholder.