Puts a jailbroken PS5 on your Tailscale network.
- Reach the console from anywhere. FTP, the payload loader, web tools: whatever listens on the console is available at its tailnet address from your other Tailscale devices.
- Remote Play over Tailscale. Play the PS5 from anywhere with a Remote Play client, at the console's tailnet address, with no port forwarding on your router.
- Stream games to the console over Tailscale. A Moonlight client on the PS5 (such as ProsperoLight) can connect to a Sunshine host on your tailnet.
- Home screen icon that opens its status page.
It runs the real Tailscale client code (v1.104.0) as a single payload.
Unofficial and experimental. This project is not affiliated with or endorsed by Tailscale Inc. or Sony. It has been tested on one console. It runs homebrew with full privileges on a jailbroken system; use it at your own risk.
The PS5 kernel has no tunnel device, so Tailscale cannot become a system-wide VPN here. It runs inside one process:
- Games and PSN traffic do not go through Tailscale.
- Apps and the browser on the console cannot open tailnet addresses, and the console's own IP address does not change. They can reach a device on your tailnet through an address on the console itself: see reaching a device from the PS5 and game streaming.
- No subnet routing, Tailscale SSH or Funnel. Taildrop works for receiving files, not for sending them.
- The console cannot use an exit node, and it does not offer itself as one. Offering one is doable (it needs no tunnel device), but the PS5 would make a terrible exit node: every packet would pass through this one low-priority process, so it would be slow, and it would fall away whenever the console goes into rest mode, reboots or loses its jailbreak, taking the internet of every device using it with it. Use a PC, a server or a router on your tailnet instead.
- A jailbroken PS5 with an ELF loader listening on port 9021 (for example elfldr).
- A Tailscale account.
Tested on firmware 13.42 with elfldr 0.26.
There is one file, tailscale-<version>.elf (for example
tailscale-0.6.1.elf), and it is an ordinary payload: running it starts
Tailscale. The examples below call it tailscale.elf; use the name of the
file you downloaded, or rename it.
-
Download
tailscale-<version>.elffrom the latest release. The.sigfile next to it is for the status page's Install button; you do not need it. -
Send it to the console's ELF loader. Any payload sender works:
# Linux / macOS socat -t 60 - TCP:<console-ip>:9021 < tailscale.elf
# Windows (script from this repository) .\tools\ps5send.ps1 -File tailscale.elf -PS5Host <console-ip>
-
Open
http://<console-ip>:8090on a phone or PC. Scan the QR code or follow the link and log in to Tailscale. If your tailnet uses device approval, approve the console in the admin console.
The console now has a tailnet address, shown on the status page. The first run also adds a Tailscale icon to the home screen (media section) that opens the status page.
Tailscale runs until the console restarts. After a restart and jailbreak, send the file again; the login and settings are kept. If you use a payload manager or autoloader, add the file there like any other payload.
To update, use the new file in place of the old one. Sending it while Tailscale is running replaces the running copy. The file name changes with every release, so check your autoload settings after updating: a payload manager or autoloader that still points at the old file starts the old version with the console, or nothing if you deleted it.
The easy way to avoid that: keep the copy your payload manager or autoloader
starts under a fixed name without a version, such as tailscale.elf. When
you update, save the new release over it under that same name, and the
autoload entry never needs to change. The status page always shows which
version is really running.
Connect to the console's tailnet address or MagicDNS name on the port you want, for example FTP on 2121 or the payload loader on 9021.
- Every TCP port that something on the console listens on is forwarded. Ports with no listener refuse the connection.
- UDP ports have to be listed, under Settings on the status page. The default list is Remote Play's.
- To keep a TCP port off the tailnet, list it under "TCP ports never exposed" in the settings.
The console's own Remote Play service is reachable at its tailnet address, so a Remote Play client on any of your Tailscale devices can connect from anywhere. Any client that lets you enter the console's address works.
- On the console, enable Remote Play (Settings > System > Remote Play).
- Register your Remote Play client with the console as usual. This is easiest at home on the same network; see the client's documentation.
- In the client, add the console manually with its tailnet address (shown on the status page).
- Connect.
Tested and working with Chiaki, and with Asobi on iOS and Android.
How it works: Remote Play uses TCP 9295 and UDP 9295, 9296, 9297 and 9302. The TCP port is forwarded like any other; the daemon listens on the UDP ports on the console's tailnet addresses and relays them to the service.
Notes:
- The video passes through the daemon, which by default runs at the lowest priority so that it never takes time from a game. If the stream stutters under a demanding game, set Priority to High in the settings and start Tailscale again.
- Waking the console from rest mode does not work: nothing runs while it sleeps, so it is not on the tailnet then. Tailscale carries on by itself once the console is awake again.
Send a file to the console from any of your own devices with Tailscale's
Taildrop: the share menu on a phone, Send with Tailscale on a desktop, or
tailscale file cp <file> <console name>:. The file lands in
/data/tailscale/received on the console (changeable in the settings), a
notification says so, and the status page lists it under Received files
with a download link. A file with the same name as an earlier one gets a
number; nothing is overwritten.
Taildrop only works between devices logged in as the same user; that is Tailscale's rule. Sending files from the console is not implemented.
http://<console address>:8090, on the LAN or over the tailnet, or the
Tailscale icon on the home screen. It shows the connection state, the
login link and your devices, and has the game streaming hosts, the settings,
and buttons for logging out, stopping and uninstalling.
Updates. When a newer release exists the page says so, and the console shows a notification once. Install downloads the release, checks that it is signed with this project's release key and is the version it claims to be, and starts it; the login and settings are kept. Nothing is ever installed without that button being pressed. It needs an ELF loader on port 9021, like sending the payload by hand does.
Installing from the page replaces the copy that is running, not the file
your payload manager or autoloader starts with the console. Check your
autoload settings after updating, or the old version is back after the
next restart. To have that file replaced as well, put its path under
Payload file to keep up to date in the settings, for example
/data/pldmgr/payloads/Tailscale/tailscale.elf. The file keeps the name it
has there, whatever version is in it, so your autoload entry keeps working.
That is one more reason to give that copy a fixed name such as
tailscale.elf and not the versioned name it was downloaded under.
0.6.0 looks for a file named plain tailscale.elf, which 0.6.1 and 0.6.2 do
not have; 0.7.0 carries it as well, so 0.6.0 can install 0.7.0 from the
page.
Devices. The list is grouped into your tailnet's devices and devices shared with you, and marks the ones that can be used as an exit node. It can be searched (name, address, OS, tag, place) and limited to devices that are online. If your tailnet has a VPN add-on such as Mullvad, its exit servers are counted but kept out of the list until you tick Show VPN exit servers. Click an address to copy it.
Under a device's name the page says how the console currently reaches it: direct, or relayed through one of Tailscale's relay servers, which is slower and worth knowing when a stream stutters. Devices with no recent traffic show nothing. test measures the connection there and then and shows the round-trip time; it also wakes a connection that was idle.
Key expiry. The page shows when the console's Tailscale key expires. By default that is 180 days after logging in, and an expired key takes the console off your tailnet until someone presses Log in again. From two weeks before, the page and a notification on the console warn about it. To avoid it altogether, open the Tailscale admin console, find the console in the list of machines and choose Disable key expiry.
Password. Out of the box the page has no password, like the console's
other homebrew services: anyone on your LAN, or on your tailnet if your ACLs
allow it, can use it. Set one under Settings. It is then asked for on
every device except the console itself. If you forget it, delete the
passwordHash line from /data/tailscale/config.json.
Settings. The name on the tailnet, the password, who on the tailnet may connect, which UDP ports are reachable and which TCP ports are not, extra forwards, the HTTP proxy, the folder for received files, the payload file to keep up to date, the priority, and update checks. Most take effect when saved; the page says which ones need Tailscale to be started again.
This lets a Moonlight client on the PS5 stream from a PC that is somewhere else, over Tailscale.
On the PC:
- Install Sunshine.
- Install Tailscale and log in to the same tailnet as the console.
On the console:
- Open the status page and find Game streaming.
- Press Add a host, enter the device that runs Sunshine and press Save. The page then shows what to enter in Moonlight.
- In your Moonlight client on the PS5, add a host manually with the address
127.0.0.1. Do not enter the PC's tailnet address: the client cannot reach it. - Pair as usual: the client shows a PIN, which you enter in Sunshine's web interface on the PC.
How it works: the daemon listens on 127.0.0.1 on Sunshine's ports (by
default TCP 47984, 47989, 48010 and UDP 47998, 47999, 48000, 48002) and
relays them to the host through Tailscale. To the Moonlight client the
Sunshine host appears to be the console itself.
More than one host, or a host that does not use the default port:
- If a host's Sunshine is set to another port (Sunshine's "Port" setting),
enter that port next to the host. In Moonlight, add
127.0.0.1:<port>. - Several hosts can be forwarded at once, but they all appear on
127.0.0.1, so each needs its own port: give every host a different port in Sunshine, at least 30 apart (for example 47989 and 48989).
Notes:
- A wired connection on the console helps, as with any streaming.
- Do not change the console's network (Wi-Fi to Ethernet, connection settings) while a stream is running. That froze the test console once; the cause was not established.
- Tested with ProsperoLight.
The PS5's browser and apps cannot open a tailnet address such as
100.64.0.4 or my-nas. To reach a service on one of your devices from the
console:
- On the status page, under Reach a device from this PS5, press Add a device. Enter the device (its tailnet name or address) and the port the service uses, and press Save.
- The page shows the address to use on the PS5, for example
127.0.0.1:8096. Open that in the PS5's browser, or enter it in the app.
Each line covers one port of one device, TCP or UDP. The port on the console
is the same as on the device where that is possible; a port below 1024 gets
8000 added (80 becomes 8080). Pages that redirect to their own name will not
follow, and HTTPS sites complain about the certificate, because the browser
sees 127.0.0.1; plain HTTP services work best, and the tailnet encrypts
the connection anyway.
test next to a line tries the connection the way the PS5 would make it and says what happened: the device answers, the device answered but nothing listens on that port, there is no answer (off, asleep or firewalled), or there is no device with that name. The game streaming hosts have the same link, which checks whether Sunshine answers.
A sleeping PC cannot be reached over Tailscale, because nothing on it is running. The console is on the same home network, though, and can send it a Wake-on-LAN packet:
- Under Wake a device at home on the status page, press Add a
device, give it a name and its network card's MAC address (on Windows,
the "Physical address" in
ipconfig /all), and press Save. - From wherever you are, open the status page over Tailscale and press Wake. Give the PC half a minute, then connect to it.
The PC needs Wake-on-LAN turned on, in its firmware setup and in the network card's settings, and it works most reliably over a cable. The console sends the packet to every device on its network; there is no reply, so the page cannot tell whether the PC heard it. Only devices in the list can be woken.
The daemon can also run an HTTP proxy that reaches tailnet hosts, for apps
that have their own proxy setting. It is off unless you give it an address in
the settings (for example 127.0.0.1:8118). Do not set it as the PS5's
system proxy. The system then sends everything through it, including pages
on 127.0.0.1, and it is not running until Tailscale has been loaded. On the
test console that stopped another homebrew tool's page from opening.
Use Settings on the status page. The settings are stored in
/data/tailscale/config.json, which can also be edited by hand; start
Tailscale again (send the payload) to apply hand edits. Every field is
optional.
{
"hostname": "ps5",
"authKey": "",
"webAddr": ":8090",
"passwordHash": "",
"httpProxyAddr": "",
"controlURL": "",
"sunshineHosts": [
{"host": "gaming-pc"},
{"host": "office-pc", "port": 48989}
],
"forwards": [
{"proto": "tcp", "listen": "127.0.0.1:8096", "target": "my-nas:8096"}
],
"udpPorts": [9295, 9296, 9297, 9302],
"blockedPorts": [],
"allowFrom": "",
"receiveDir": "/data/tailscale/received",
"payloadPath": "",
"priority": "",
"checkUpdates": true,
"verbose": false
}| Field | Meaning |
|---|---|
hostname |
The console's name on the tailnet. |
authKey |
A Tailscale auth key, to log in without the browser step. File only. |
webAddr |
Where the status page listens. |
passwordHash |
The status page's password, hashed. Set it on the status page; delete the field to remove a forgotten password. |
httpProxyAddr |
Where the HTTP proxy listens. Empty, the default, is off. |
controlURL |
A coordination server other than Tailscale's. File only. |
sunshineHosts |
The Sunshine hosts and, where it is not 47989, their port. |
forwards |
What "Reach a device from this PS5" sets up: proto is tcp or udp, listen the address on the console, target a tailnet device and port. |
udpPorts |
The console's UDP ports reachable from the tailnet. Default [9295, 9296, 9297, 9302] (Remote Play). [] turns inbound UDP off. |
blockedPorts |
Local TCP ports that are never exposed to the tailnet. |
allowFrom |
"own" lets only devices logged in as the same user as the console connect; other users' devices and devices shared into the tailnet are turned away. Anything else is the default: every device your tailnet's access rules allow. If the console is tagged, "own" means the devices of its own tailnet. |
receiveDir |
Where files sent to the console with Taildrop are put. |
wake |
Devices the status page can wake with Wake-on-LAN: a name and the network card's mac each. |
payloadPath |
The copy of the payload that is started with the console, if there is one; best kept under a fixed name such as tailscale.elf. An update installed from the status page replaces its contents and leaves its name alone. Empty: none. |
priority |
"high" lets the daemon compete with games for CPU time; anything else is the default, low. Applied when Tailscale starts. |
checkUpdates |
Ask GitHub twice a day whether a newer release exists, to show it on the status page and announce it once on the console. Nothing is downloaded. |
verbose |
Put Tailscale's own log in the main log as well. |
Files on the console:
| Path | What |
|---|---|
/data/tailscale/config.json |
Settings. |
/data/tailscale/state/ |
Tailscale's state, including the login. |
/data/tailscale/tailscale.log |
The daemon's log, rotated at 2 MB. |
/data/tailscale/tailscale-debug.log |
Tailscale's detailed log, up to 4 MB plus one older file. |
/data/tailscale/received/ |
Files received with Taildrop. |
/data/tailscale/launcher.log |
What the payload did before Tailscale itself started. The place to look when nothing seems to happen. |
/data/tailscale/icon-installed |
Marks that the home screen icon was added. Delete it to have the icon added again on the next start. |
/data/tailscale/icon-helper.elf |
The small payload that adds and removes the icon. |
/user/app/TSCL00001/ |
The home screen icon. |
Press Uninstall on the status page. It removes the home screen icon, logs
the console out of your tailnet, deletes /data/tailscale (login, settings,
logs) and stops Tailscale.
Left to do by hand:
- Remove the device in the Tailscale admin console.
- If you added
tailscale.elfto a payload manager or autoloader, remove it there, or it starts again on the next boot.
- Nothing happens when the payload is sent. If the payload cannot start,
it says why in a notification on the console and in
/data/tailscale/launcher.log; fetch that file over FTP. A payload manager does not show what a payload prints, so sending it from a PC shows more:socat -t 30 - TCP:<console>:9021 < tailscale.elf, or.\tools\ps5send.ps1 -File tailscale.elf -PS5Host <console>on Windows. If the log ends with "starting the Go program" and no status page appears, whatever follows that line is the crash report to send. - The status page does not open on the console, but does from a PC. Check that the PS5's proxy server setting is "Do Not Use".
- The Moonlight client cannot find the host. The host to add is
127.0.0.1(or127.0.0.1:<port>for a host on another port), and the Sunshine host must be listed on the status page with the port its Sunshine uses. - It stays on "Starting", or the update check never finds anything. The
daemon looks names up itself: at a DNS payload on the console
(
127.0.0.1:53) if one is running, otherwise at your router, otherwise at a public resolver (1.1.1.1, 8.8.8.8, 9.9.9.9). The log says which one it uses in a line starting withDNS:. If none answers, the console has no working internet connection for payloads. The DNS server set in the PS5's network settings plays no part. The status page shows the one in use under "Name lookups". - A stream or Remote Play stutters. Look at the device in the list on the status page and press test. "Relayed" means the two devices could not connect directly and the traffic takes a detour; that is usually a router or firewall on one side blocking UDP.
- "Not logged in" after logging in. Press Log in again for a fresh link.
- Forgot the status page password. Delete the
passwordHashline from/data/tailscale/config.jsonand start Tailscale again, or use the page on the console itself, where no password is asked. - Something else. Press Download diagnostics on the status page and
attach the file to your bug report. It holds the logs, the version, the
firmware and the settings. E-mail addresses, your tailnet's name, public IP
addresses, keys and the password are removed from it; device names and
tailnet addresses are not, so read it before posting. If the status page
never comes up, there is nothing to press: fetch
/data/tailscale/launcher.logover FTP instead.
- The status page and its controls have no password until you set one. With a password, only the console itself gets in without it. The page is served over plain HTTP: on the LAN the password travels unencrypted, over the tailnet Tailscale encrypts it.
- All listening TCP ports on the console, and the UDP ports in
udpPorts, become reachable from your tailnet. That includes the payload loader, which runs anything sent to it. If other people use your tailnet or share devices into it, set Who on the tailnet may connect to your own devices only, use Tailscale ACLs, or list ports under "TCP ports never exposed". - The local forwards and the proxy are for the console's own apps and are not exposed to the tailnet.
- With update checks on, the console contacts
api.github.comtwice a day. - Name lookups by the daemon go to the console's DNS payload if there is one, otherwise to your router or a public resolver. They are only the daemon's own lookups (Tailscale's servers, GitHub, what you send through the HTTP proxy), not the console's.
- An update is only installed when Install is pressed, and only if it carries a valid signature made with the project's release key, which is not kept on GitHub. A release put up by someone who got into the GitHub account, or changed on the way, is refused.
- Files received with Taildrop come only from devices logged in as the same user. The status page hands them out as downloads and never displays them.
About 60 MB of memory and next to no CPU when idle. By default the daemon runs at the lowest scheduling priority on at most 4 cores, so it gives way to games. With the priority set to High it shares those cores with games on equal terms.
Tested on the one console: first run and upgrade, login with device approval, starting again after a reboot with the saved login, reaching the console over the tailnet, a ProsperoLight stream from a Sunshine host through the forward, two forwarded hosts on different ports (with a stand-in for the second), the HTTP proxy, adding and removing the home screen icon, the password from the LAN and the tailnet, changing settings from the page, both priority settings, the update check, installing an update from the page (rehearsed with a test release, including replacing a second copy of the payload), receiving files with Taildrop, reaching a device through a forward set up on the page, the connection tests, the diagnostics file, limiting connections to your own devices (with the console's owner's devices only; a refusal has not been seen for real), a stay in rest mode, both a minute and nine and a half hours: the same process carried on and was back on the tailnet after waking.
Remote Play through the tailnet address works with Chiaki and with Asobi on iOS and Android.
Not tested: whether a PC actually wakes from the Wake button (the console reports sending the packets; no sleeping PC was at hand), switching between Wi-Fi and Ethernet while running, rest mode on Wi-Fi, the complete Uninstall on a console (its parts were tested separately), whether High priority improves Remote Play, a real Sunshine host on a non-default port, other firmware versions, coordination servers other than Tailscale's.
See docs/BUILDING.md. The build runs on Windows with PowerShell, clang, ps5-payload-sdk and a patched Go 1.27.1.
tailscale.elf is a small C program built with
ps5-payload-sdk with a Go program
embedded in it. The SDK's startup code lifts the PS5's restriction on where
system calls may be issued from. The C part drops the process to the lowest
scheduling priority, maps the Go program into memory and enters it the way
the FreeBSD kernel would. Go's runtime and standard library are patched to
speak the PS5's FreeBSD 9 era system call interface. The Go program is a
tsnet server that pipes every
incoming tailnet TCP connection to the same port on localhost.
docs/TECHNICAL.md has the details, including what was learned about running Go on the PS5.
- Tailscale, whose client this runs.
- John Törnblom's ps5-payload-sdk and elfldr.
- itsPLK's Payload Manager, whose app-icon installer this follows.
The project's own code is licensed under the GNU General Public License
version 3 or later (see LICENSE); the payloads link the SDK's
GPL-licensed startup code. patches/ is a patch to the Go source tree and is
under Go's BSD-style license. Tailscale is BSD-3-Clause. The release binaries
also contain Tailscale's dependencies under their own licenses; they are
listed in tsd/go.mod.
"Tailscale" is a trademark of Tailscale Inc. "PlayStation" and "PS5" are trademarks of Sony Interactive Entertainment Inc.
