> ## Documentation Index
> Fetch the complete documentation index at: https://developer.upsun.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Run Tailscale on Upsun: Create secure VPN connections for your applications

> Learn how to integrate Tailscale VPN with your Upsun applications to create secure, private network connections between your containers and infrastructure.


export const PostMeta = ({data = {}}) => {
  const {author, date, image} = data;
  const authors = Array.isArray(author) ? author : author ? [author] : [];
  const resolveAuthor = slug => {
    const entry = AUTHOR_MAP[slug] || ({});
    const name = entry.name || slug;
    const github = entry.github || null;
    const linkedin = entry.linkedin || null;
    const url = github ? `https://github.com/${github}` : linkedin || null;
    const avatarUrl = github ? `https://github.com/${github}.png?size=64` : null;
    return {
      name,
      url,
      avatarUrl
    };
  };
  const formattedDate = date ? new Date(date).toLocaleDateString('en-US', {
    year: 'numeric',
    month: 'long',
    day: 'numeric'
  }) : null;
  if (!image && authors.length === 0 && !formattedDate) return null;
  const AUTHOR_MAP = {
    "aaron-collier": {
      "name": "Aaron Collier"
    },
    "aaron-dudenhofer": {
      "name": "Aaron Dudenhofer"
    },
    "aaron-porter": {
      "name": "Aaron Porter"
    },
    "adriaan-odendaal": {
      "name": "Adriaan Odendaal"
    },
    "ajmal": {
      "name": "Ajmal Siddiqui"
    },
    "akalipetis": {
      "name": "Antonis Kalipetis"
    },
    "alexander-varwijk": {
      "name": "Alexander Varwijk"
    },
    "alicia-bevilacqua": {
      "name": "Alicia Bevilacqua"
    },
    "amelie-deguerry": {
      "name": "Amelie Deguerry"
    },
    "anacidre": {
      "name": "Ana Cidre",
      "linkedin": "https://www.linkedin.com/in/ana-cidre"
    },
    "andoni": {
      "name": "Andoni Auzmendi"
    },
    "andrei-taranu": {
      "name": "Andrei (Alex) Taranu",
      "linkedin": "https://www.linkedin.com/in/andrei-alex-taranu/"
    },
    "andrew-baxter": {
      "name": "Andrew Baxter"
    },
    "andrew-melck": {
      "name": "Andrew Melck"
    },
    "antoine-crochet-damais": {
      "name": "Antoine Crochet Damais"
    },
    "augustin-delaporte": {
      "name": "Augustin Delaporte",
      "linkedin": "https://www.linkedin.com/in/augustindelaporte/"
    },
    "branislav-bujisic": {
      "name": "Branislav Bujisic"
    },
    "carl-smith": {
      "name": "Carl Smith"
    },
    "caroline-leroy": {
      "name": "Caroline Leroy"
    },
    "cati-mayer": {
      "name": "Cati Mayer"
    },
    "catplat": {
      "name": "C Trinkwon"
    },
    "ceelolulu": {
      "name": "Celeste van der Watt"
    },
    "chadwcarlson": {
      "name": "Chad Carlson",
      "github": "chadwcarlson",
      "linkedin": "https://www.linkedin.com/in/chadwcarlson"
    },
    "chris-ward": {
      "name": "Chris Ward"
    },
    "chris-yates": {
      "name": "Chris Yates"
    },
    "christian-sieber": {
      "name": "Christian Sieber"
    },
    "christopher-lockheardt": {
      "name": "Christopher Lockheardt"
    },
    "christopher-skene": {
      "name": "Christopher Skene"
    },
    "chuck-morgan": {
      "name": "Chuck Morgan"
    },
    "corey-dockendorf": {
      "name": "Corey Dockendorf"
    },
    "crell": {
      "name": "Crell"
    },
    "damz": {
      "name": "Damz"
    },
    "dan-morrison": {
      "name": "Dan Morrison"
    },
    "davidbonachera": {
      "name": "David Bonachera",
      "github": "davidbonachera",
      "linkedin": "https://www.linkedin.com/in/davidbonachera"
    },
    "dereliahmet1": {
      "name": "Ahmet Faruk Dereli"
    },
    "devicezero": {
      "name": "Jonas Kröger",
      "github": "devicezero",
      "linkedin": "https://www.linkedin.com/in/jonaskroeger/"
    },
    "doug-goldberg": {
      "name": "Doug Goldberg"
    },
    "duncan-naves": {
      "name": "Duncan Naves",
      "github": "duncannaves",
      "linkedin": "https://www.linkedin.com/in/duncan-naves-a94423aa"
    },
    "erika-bustamante": {
      "name": "Erika Bustamante"
    },
    "fabpot": {
      "name": "Fabien Potencier"
    },
    "flovntp": {
      "name": "Florent Huck",
      "github": "flovntp",
      "linkedin": "https://www.linkedin.com/in/florenthuck"
    },
    "fred-plais": {
      "name": "Fred Plais"
    },
    "gauthier-garnier": {
      "name": "Gauthier Garnier"
    },
    "gilzow": {
      "name": "Paul Gilzow"
    },
    "gmoigneu": {
      "name": "Guillaume Moigneu",
      "github": "gmoigneu",
      "linkedin": "https://www.linkedin.com/in/guillaumemoigneu/"
    },
    "gregqualls": {
      "name": "Greg Qualls"
    },
    "guguss": {
      "name": "Augustin Delaporte"
    },
    "haylee-millar": {
      "name": "Haylee Millar"
    },
    "ivana-kotur": {
      "name": "Ivana Kotur"
    },
    "jackrabbithanna": {
      "name": "Mark Hanna"
    },
    "jared-wright": {
      "name": "Jared Wright",
      "github": "jww-sh",
      "linkedin": "https://www.linkedin.com/in/jaredwaynewright"
    },
    "jessica-orozco": {
      "name": "Jessica Orozco"
    },
    "joey-stanford": {
      "name": "Joey Stanford"
    },
    "john-grubb": {
      "name": "John Grubb"
    },
    "jonas-kruger": {
      "name": "Jonas Kruger"
    },
    "kathryn-frazer": {
      "name": "Kathryn Frazer"
    },
    "kemiojo": {
      "name": "Kemi Elizabeth Ojogbede"
    },
    "kieronsambrook-smith": {
      "name": "Kieronsambrook Smith"
    },
    "laurent-arnoud": {
      "name": "Laurent Arnoud"
    },
    "letoya-boyne": {
      "name": "Letoya Boyne"
    },
    "lolautruche": {
      "name": "Jérôme Vieilledent"
    },
    "lyly-lepinay": {
      "name": "Lyly Lepinay"
    },
    "manauwar-alam": {
      "name": "Manauwar Alam"
    },
    "marc-antoine-porri": {
      "name": "Marc Antoine Porri"
    },
    "maria-antinkaapo": {
      "name": "Maria Antinkaapo"
    },
    "maria-de-anton": {
      "name": "Maria De Anton"
    },
    "mark-dorison": {
      "name": "Mark Dorison"
    },
    "markus-hausammann": {
      "name": "Markus Hausammann"
    },
    "mary-thomas": {
      "name": "Mary Thomas"
    },
    "mathias-bolt-lesniak": {
      "name": "Mathias Bolt Lesniak"
    },
    "mathieu-strauch": {
      "name": "Mathieu Strauch"
    },
    "matthias-van-woensel": {
      "name": "Matthias Van Woensel",
      "linkedin": "https://www.linkedin.com/in/matthias-van-woensel-267a069"
    },
    "michael-sharp": {
      "name": "Michael Sharp"
    },
    "mupsi": {
      "name": "Marine Gandy"
    },
    "natalie-harper": {
      "name": "Natalie Harper"
    },
    "ngommenginger": {
      "name": "Nicolas Gommenginger",
      "linkedin": "https://www.linkedin.com/in/nicolas-gommenginger"
    },
    "nicholas-bennison": {
      "name": "Nicholas Bennison"
    },
    "nicholas-vahalik": {
      "name": "Nicholas Vahalik"
    },
    "nick-hardiman": {
      "name": "Nick Hardiman"
    },
    "nickanderegg": {
      "name": "Nickanderegg"
    },
    "nicolas-grekas": {
      "name": "Nicolas Grekas",
      "github": "nicolas-grekas",
      "linkedin": "https://www.linkedin.com/in/nicolasgrekas/"
    },
    "niti-malwade": {
      "name": "Niti Malwade"
    },
    "opensocialteam": {
      "name": "Opensocialteam"
    },
    "ori-pekelman": {
      "name": "Ori Pekelman"
    },
    "otavio-santana": {
      "name": "Otavio Santana"
    },
    "palwandi": {
      "name": "Pawan Alwandi",
      "github": "pawpy",
      "linkedin": "https://www.linkedin.com/in/pawanalwandi"
    },
    "patrick-boest": {
      "name": "Patrick Boest"
    },
    "patrick-dawkins": {
      "name": "Patrick Dawkins",
      "github": "pjcdawkins",
      "linkedin": "https://www.linkedin.com/in/patrickdawkins"
    },
    "patrick-klima": {
      "name": "Patrick Klima"
    },
    "pjcdawkins": {
      "name": "Pjcdawkins"
    },
    "prineet-kaurbhurji": {
      "name": "Prineet Kaurbhurji"
    },
    "quentin-sinig": {
      "name": "Quentin Sinig"
    },
    "ralt": {
      "name": "Florian Margaine",
      "github": "ralt",
      "linkedin": "https://www.linkedin.com/in/florian-margaine-43971136"
    },
    "ramanathanramakrishnamurthy": {
      "name": "Ramanathanramakrishnamurthy"
    },
    "remi-lejeune": {
      "name": "Rémi Lejeune"
    },
    "ribel": {
      "name": "Taras Kruts"
    },
    "robert-douglass": {
      "name": "Robert Douglass"
    },
    "rudy-weber": {
      "name": "Rudy Weber"
    },
    "ryan-hicks": {
      "name": "Ryan Hicks"
    },
    "sabri-helal": {
      "name": "Sabri Helal"
    },
    "savannah-bergeron": {
      "name": "Savannah Bergeron"
    },
    "shannon-vettes": {
      "name": "Shannon Vettes"
    },
    "shawn-ogasawara": {
      "name": "Shawn Ogasawara",
      "linkedin": "https://www.linkedin.com/in/shawn-ogasawara-83a9a0/"
    },
    "shawna-spoor": {
      "name": "Shawna Spoor"
    },
    "shedrack-akintayo": {
      "name": "Shedrack Akintayo"
    },
    "simon-ruggier": {
      "name": "Simon Ruggier"
    },
    "sophie-van-der-kindere": {
      "name": "Sophie Van Der Kindere"
    },
    "stefanos-thampis": {
      "name": "Stefanos Thampis"
    },
    "stephen-weinberg": {
      "name": "Stephen Weinberg"
    },
    "sukhman-virk": {
      "name": "Sukhman Virk"
    },
    "sumaira-nazir": {
      "name": "Sumaira Nazir"
    },
    "sumer": {
      "name": "Sümer Cip"
    },
    "syed-raza": {
      "name": "Syed Raza"
    },
    "tamara-bacchia": {
      "name": "Tamara Bacchia"
    },
    "tara-arnold": {
      "name": "Tara Arnold"
    },
    "theosakamg": {
      "name": "Mickael Gaillard",
      "github": "theosakamg"
    },
    "thomasdiluccio": {
      "name": "Thomas di Luccio"
    },
    "tim-anderson": {
      "name": "Tim Anderson"
    },
    "tom-helmer-hansen": {
      "name": "Tom Helmer Hansen"
    },
    "tylermills": {
      "name": "Tyler Mills"
    },
    "upsun": {
      "name": "Upsun"
    },
    "veronika-tolkachova": {
      "name": "Veronika Tolkachova",
      "linkedin": "https://www.linkedin.com/in/veronika-tolkachova-169167a2"
    },
    "vince-parker": {
      "name": "Vince Parker"
    },
    "vinnie-russo": {
      "name": "Vincenzo Russo"
    },
    "vrobert78": {
      "name": "Vincent Robert",
      "github": "vrobert78",
      "linkedin": "https://www.linkedin.com/in/vincent-robert-498a883"
    },
    "yuriy-babenko": {
      "name": "Yuriy Babenko"
    },
    "yuriy-gerasimov": {
      "name": "Yuriy Gerasimov"
    }
  };
  return <div className="post-meta">
      {(authors.length > 0 || formattedDate) && <div className="post-meta-info">
          {authors.length > 0 && <div className="post-meta-authors">
              {authors.map(slug => {
    const {name, url, avatarUrl} = resolveAuthor(slug);
    const inner = <>
                    {avatarUrl && <img src={avatarUrl} alt={name} className="post-meta-avatar" />}
                    <span className="post-meta-author-name">{name}</span>
                  </>;
    return url ? <a key={slug} href={url} target="_blank" rel="noopener noreferrer" className="post-meta-author">
                    {inner}
                  </a> : <span key={slug} className="post-meta-author">{inner}</span>;
  })}
            </div>}
          {authors.length > 0 && formattedDate && <span className="post-meta-separator" aria-hidden="true">·</span>}
          {formattedDate && <span className="post-meta-date">{formattedDate}</span>}
        </div>}
      {image && <img src={image} alt="" className="post-meta-image" aria-hidden="true" />}
    </div>;
};

<PostMeta data={{ author: ["ralt"], date: "2025-09-16T00:00:00+00:00", image: "/images/posts/hands-on/run-tailscale-on-upsun-secure-vpn-connections/run-tailscale-on-upsun-secure-vpn-connections.webp" }} />

[Tailscale](https://tailscale.com/) is a VPN service that creates secure, private networks between your devices and applications. By integrating Tailscale with your Upsun applications, you can make your containers part of a "tailnet" - Tailscale's term for their private networks.

This guide demonstrates how you can leverage Tailscale to connect to external endpoints over a secure network. An example Go application will be added as a placeholder and example for your own application. This setup enables secure communication between your application and other resources in your tailnet.

## Prerequisites

Before starting, you'll need:

* A [Tailscale](https://tailscale.com/) account with an auth key
* Basic familiarity with Upsun configuration
* Understanding of Go applications

## Tailscale implementation limitations

Tailscale usually relies on a specific tunnel networking device (`tun0`) to route traffic from their machine to their network. Because Upsun run containers directly, a userspace application can't add a new network interface. To work around this limitation, Tailscale can be started as a HTTP proxy to the tailnet. Our example application will connect to the external endpoints via this exposed HTTP proxy.

## Create a basic Go application

Start by creating a simple Go application. Create a `go.mod` file:

```go {filename="go.mod"} theme={null}
module example-app
go 1.25
```

Create a `main.go` file:

```go {filename="main.go"} theme={null}
package main

import (
	"fmt"
	"net/http"
	"os"
)

func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("Hello, Upsun!"))
	})
	err := http.ListenAndServe(fmt.Sprintf(":%s", os.Getenv("PORT")), nil)
	panic(err)
}
```

Create your initial Upsun configuration in `.upsun/config.yaml`:

```yaml {filename=".upsun/config.yaml"} theme={null}
routes:
  https://{default}:
    type: upstream
    upstream: app:http

applications:
  app:
    type: golang:1.25
    web:
      commands:
        start: ./hello
      locations:
        '/':
          passthru: true
    hooks:
      build: go build -o hello
```

This basic configuration creates a working example Go application on Upsun. You now need to add the Tailscale integration to the project.

## Configure multiple processes with Supervisor

Since Upsun's `web.commands.start` is designed to run only one process, you will use [Supervisor](https://supervisord.org/) to start and manage both your application and the Tailscale daemon processes. Update your `.upsun/config.yaml`:

```yaml {filename=".upsun/config.yaml"} theme={null}
routes:
  https://{default}:
    type: upstream
    upstream: app:http

applications:
  app:
    type: golang:1.25
    dependencies:
      python3:
        supervisor: '*'
    web:
      commands:
        start: supervisord -n -c supervisor.conf
      locations:
        '/':
          passthru: true
    hooks:
      build: go build -o hello
```

Create a `supervisor.conf` file to manage your processes:

```ini {filename="supervisor.conf"} theme={null}
[supervisord]
logfile=/tmp/supervisord.log
nodaemon=true
minfds=1024
pidfile=/tmp/supervisord.pid

[program:hello]
command=/app/hello
process_name=%(program_name)s
autostart=true
autorestart=true
```

## Download and configure Tailscale

The easiest way to run Tailscale is to download and run the `amd64` binary directly. Create a script to download Tailscale and cache it between builds. Create `scripts/download-tailscale.sh`:

```bash {filename="scripts/download-tailscale.sh"} theme={null}
#!/bin/bash

set -e

version=1.86.2

# This uses $PLATFORM_CACHE_DIR to avoid re-downloading the binary on every build
cache="$PLATFORM_CACHE_DIR"/tailscale_"$version"_amd64.tgz

if [ ! -f "$cache" ]; then
    curl https://pkgs.tailscale.com/stable/tailscale_"$version"_amd64.tgz -o "$cache"
fi

tar xf "$cache"
mv tailscale_"$version"_amd64 tailscale
```

Make the script executable:

```bash {filename="Terminal"} theme={null}
chmod +x scripts/download-tailscale.sh
```

Update your build hook in `.upsun/config.yaml` to use this script:

```yaml {filename=".upsun/config.yaml"} theme={null}
applications:
  app:
    # ... rest of configuration ...
    hooks:
      build: |
        set -e
        ./scripts/download-tailscale.sh
        go build -o hello
```

## Add Tailscale daemon to Supervisor

Update your `supervisor.conf` file to include the Tailscale daemon:

```ini {filename="supervisor.conf"} theme={null}
[supervisord]
logfile=/tmp/supervisord.log
nodaemon=true
minfds=1024
pidfile=/tmp/supervisord.pid

[program:hello]
command=/app/hello
process_name=%(program_name)s
autostart=true
autorestart=true

[program:tailscale]
command=/app/tailscale/tailscaled --state=mem: --socket=/tmp/tailscaled.sock --tun=userspace-networking --outbound-http-proxy-listen=0.0.0.0:8080 --socks5-server=0.0.0.0:1080
process_name=%(program_name)s
autostart=true
autorestart=true
```

Key configuration options:

* `--state=mem:` creates a new Tailscale client on each restart. For persistent clients, use a file on a mount instead
* `--outbound-http-proxy-listen=0.0.0.0:8080` exposes the VPN through an HTTP proxy on port 8080
* `--socks5-server=0.0.0.0:1080` provides SOCKS5 proxy access on port 1080

## Connect to your tailnet

After the Tailscale daemon starts, you need to run `tailscale up` to join your tailnet. You could add this to your `post_start` command:

```yaml {filename=".upsun/config.yaml"} theme={null}
applications:
  app:
    # ... rest of configuration ...
    web:
      commands:
        start: supervisord -n -c supervisor.conf
        post_start: /app/tailscale/tailscale --socket="$sock" up --auth-key="$TAILSCALE_AUTHKEY"
```

However, the `tailscaled` daemon needs time to start, causing `tailscale up` to fail. Instead, create a `scripts/tailscale-up.sh` script that waits for the daemon:

```bash {filename="scripts/tailscale-up.sh"} theme={null}
#!/bin/bash

set -xe

sock=/tmp/tailscaled.sock

while ! curl --unix-socket "$sock" localhost -w1; do
    sleep 0.5
done

/app/tailscale/tailscale --socket="$sock" up --auth-key="$TAILSCALE_AUTHKEY"
```

Make this script executable:

```bash theme={null}
chmod +x scripts/tailscale-up.sh
```

And update your Upsun configuration to use this script:

```yaml {filename=".upsun/config.yaml"} theme={null}
applications:
  app:
    # ... rest of configuration ...
    web:
      commands:
        start: supervisord -n -c supervisor.conf
        post_start: ./scripts/tailscale-up.sh
```

## Set up authentication

Create an environment variable with your Tailscale authentication key:

```bash theme={null}
upsun variable:create env:TAILSCALE_AUTHKEY your-auth-key-here
```

Your application container is now part of your tailnet!

## Using your tailnet connection

### Outbound connections

To connect to resources in your tailnet from your application, use the HTTP proxy on port 8080:

```bash theme={null}
# Example with curl
http_proxy=localhost:8080 curl 100.87.175.78
```

As mentioned earlier, our applications needs to configure their HTTP proxy settings to be able to connect to other machines on the tailnet. Refer to the proxy documentation of your specific runtime or framework.

### Inbound connections

Your application becomes immediately accessible from other devices in your tailnet. To expose additional services like databases, use tools like `socat` to forward traffic:

```bash theme={null}
# Forward database traffic (example)
socat tcp-listen:3306,bind=0.0.0.0,fork,reuseaddr tcp:database.internal:3306
```

## Next steps

With Tailscale running on Upsun, you can:

* Connect securely to your application from any device in your tailnet
* Access private databases and services through your application
* Create secure communication channels between multiple Upsun applications
* Implement zero-trust networking for your infrastructure

[Get started with Upsun](https://upsun.com) and create your secure, scalable application infrastructure today.
