Skip to content
agentgateway has joined the Agentic AI FoundationLearn more

For the complete documentation index, see llms.txt. Markdown versions of all docs pages are available by appending .md to any docs URL.

Page as Markdown

Tailscale

Authenticate users with their Tailscale identity for zero-trust access to your MCP servers.

Agentgateway can integrate with Tailscale to authenticate users based on their Tailscale identity, which enables zero-trust access to your MCP servers. Agentgateway uses an external authorization (extAuthz) policy to call the local Tailscale daemon’s whois API and identify the user behind each connection.

How it works

  1. The client connects from its Tailscale IP address (100.x.x.x).
  2. Agentgateway calls the Tailscale local whois API with the source IP address.
  3. Tailscale returns the node and user information.
  4. Agentgateway allows or denies the request and logs the identity.

Before you begin

Step 1: Verify that Tailscale is running

  1. Check that Tailscale is connected. You should see your machine listed with a 100.x.x.x IP address.

    tailscale status
  2. Note your Tailscale IP address. You use this address to test access later.

    tailscale ip -4

Step 2: Configure and start agentgateway

  1. Create a config.yaml file. The configuration uses an extAuthz policy to call the Tailscale daemon’s local whois API with the source IP address of each request, then extracts the node name and user email from the response. The socket path for the Tailscale daemon differs by platform.

    # yaml-language-server: $schema=https://agentgateway.dev/schema/config
    frontendPolicies:
      accessLog:
        add:
          tailscale.node: extauthz.tailscaleNode
          tailscale.email: extauthz.tailscaleEmail
    
    gateways:
      default:
        port: 3000
        protocol: HTTP
    routes:
    - name: application
      backends:
      - mcp:
          targets:
          - name: everything
            stdio:
              cmd: npx
              args: ["@modelcontextprotocol/server-everything"]
      policies:
        cors:
          allowOrigins: ["*"]
          allowHeaders: ["*"]
          exposeHeaders: ["Mcp-Session-Id"]
        extAuthz:
          host: unix:/run/tailscale/tailscaled.sock
          protocol:
            http:
              path: |
                "/localapi/v0/whois?addr=" + source.address
              addRequestHeaders:
                :authority: '"local-tailscaled.sock"'
              metadata:
                tailscaleNode: json(response.body).Node.Name
                tailscaleEmail: json(response.body).UserProfile.LoginName
    Review the following table to understand this configuration.
    SettingDescription
    frontendPolicies.accessLog.addAdds the Tailscale identity to the access logs.
    extAuthz.hostThe Unix socket path to the Tailscale daemon.
    extAuthz.protocol.http.pathA CEL expression that calls the Tailscale whois API with the client’s source IP address.
    addRequestHeaders.:authorityThe hostname that the Tailscale local API requires.
    metadata.tailscaleNodeExtracts the machine name from the Tailscale response.
    metadata.tailscaleEmailExtracts the user email from the Tailscale response.

    The value for extAuthz.host is the path to the Tailscale daemon’s local socket, which differs by platform. Use the path that matches the platform where agentgateway runs.

    PlatformSocket path
    Linux/run/tailscale/tailscaled.sock
    macOS/var/run/tailscale/tailscaled.sock
    WindowsNamed pipe (not supported through a Unix socket)

    Note

    On most Linux systems, /var/run is a symlink to /run, so /var/run/tailscale/tailscaled.sock and /run/tailscale/tailscaled.sock point to the same socket.

  2. Start agentgateway.

    agentgateway -f config.yaml

    Example output:

    info  state_manager  loaded config from File("config.yaml")
    info  app            serving UI at http://localhost:15000/ui
    info  proxy::gateway started bind  bind="bind/3000"

Step 3: Test the authentication

  1. Send a request from localhost. Because localhost does not have a Tailscale identity, the request is denied with a 403 Forbidden response.

    curl -i http://localhost:3000/mcp

    Example output:

    HTTP/1.1 403 Forbidden
    external authorization failed
  2. Send a request through your Tailscale IP address. The request passes authentication and reaches the MCP server, which returns a 406 Not Acceptable response because the request does not include the required text/event-stream header.

    TAILSCALE_IP=$(tailscale ip -4)
    curl -i http://$TAILSCALE_IP:3000/mcp

    Example output:

    HTTP/1.1 406 Not Acceptable
    Not Acceptable: Client must accept text/event-stream
  3. Send a complete MCP request through your Tailscale IP address with the required headers.

    TAILSCALE_IP=$(tailscale ip -4)
    curl -X POST "http://$TAILSCALE_IP:3000/mcp" \
      -H "Content-Type: application/json" \
      -H "Accept: text/event-stream" \
      -d '{"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}},"id":1}'
  4. Check the agentgateway logs. After a successful request, the logs show the Tailscale identity from the access log fields that you configured.

    info request ... tailscale.node=your-machine-name tailscale.email=you@example.com

Learn more

Was this page helpful?
Agentgateway assistant

Ask me anything about agentgateway configuration, features, or usage.

Note: AI-generated content might contain errors; please verify and test all returned information.

Tip: one topic per conversation gives the best results. Use the + button in the chat header to start a new conversation.

Switching topics? Starting a new conversation improves accuracy.
↑↓ navigate select esc dismiss

What could be improved?

Your feedback helps us improve assistant answers and identify docs gaps we should fix.

Need more help? Join us on Discord: https://discord.gg/y9efgEmppm

Want to use your own agent? Add the Solo MCP server to query our docs directly. Get started here: https://search.solo.io/.