Reach an external service from a fixed IP

3 min read Updated 2 months ago

Reach an external service from a fixed IP

Some third-party services (for example a managed database) only accept connections from an allowlisted IP address. Your application's pods do not have a fixed outbound IP, so they cannot be allowlisted directly.

Outbound egress solves this. Your application sends the connection through an exit node in your own tailnet, and the third party sees the exit node's fixed IP. Only the connections you choose go this way; everything else your application does is unaffected.

It also works for reaching a service that lives inside your tailnet: a target pointed at a tailnet device is reached directly, without the exit node.

Before you start

  • A Tailscale service already added to your application (egress reuses its auth key and tailnet).
  • An exit node in your tailnet: a device you control with a stable public IP, advertised and approved as an exit node.
  • In your tailnet access controls, the nodes that use the exit node must be allowed to reach the internet through it. autogroup:internet is the destination in that rule, not a permission you give the exit node (the default policy already allows this).

Turn on egress

  1. Open your application, open the Private network card, and click View connections.
  2. In the Outbound egress section, switch Enabled on.
  3. Enter the exit node as its Tailscale IP (for example 100.72.53.78).
  4. Add one target per external service you want to reach:
    • name: a label, for example partner-db.
    • host: the external address, for example db.partner.com (or a tailnet device's Tailscale IP to reach it directly).
    • port: the service port, for example 5432.
  5. Click Save egress. The egress node updates on its own; there is nothing to redeploy.

Approve the exit node on Headscale

On Tailscale an exit node is ready once you approve it in the admin. On your own Headscale, advertising an exit route is not enough: the route has to be approved before any node can use it.

headscale nodes approve-routes --identifier <node-id> --routes 0.0.0.0/0

Approving either the IPv4 or the IPv6 route approves the other automatically. You can also let a policy do it, with autoApprovers.exitNode.

This step is easy to miss because skipping it does not produce an error. Until the route is approved the traffic simply leaves the way it normally would, from your application's own outbound address, so the third party sees an address it has not allowlisted rather than the fixed IP you expected. After a later restart it turns into a startup failure instead, with the egress node reporting that the exit node is not advertising an exit node.

Point your application at it

For each target, the panel shows an address to use, for example your-app-egress:5432. Set your application's connection to that address instead of the real host:

DB_HOST=your-app-egress
DB_PORT=5432

Your application connects to that address, the traffic leaves through the exit node, and the external service sees the exit node's fixed IP (the one you allowlisted).

Notes

  • If two targets use the same port, the first keeps that port and the next uses the following one. The panel always shows the exact address for each target.
  • The exit node only carries the targets you add. All other outbound traffic from your application is untouched.
  • Egress is free and does not count towards your usage.