SYNCTHING-RELAY(7)
π syncthing-relay(7) β Relay Protocol v1
π·οΈ NAME
syncthing-relay β Relay Protocol v1
π WHAT IS A RELAY? π
Okay so bestie, a relay is literally a service that relays data between two devices that are not able to connect to each other directly otherwise. Usually this happens because both devices are behind a NAT and neither side can open a port that would be directly accessible from the internet. π
A relay was designed to relay the BEP protocol, which is why the protocol spec leans on device IDs, but at the same time itβs general enough that it could be reused by other protocols or applications. Why? Because the data transferred between two devices that use a relay is completely obscure and does not affect the relaying. β¨
π OPERATION MODES β¨
Relay listens on a single TCP socket, but it has two different connection modes, where a connection mode is a predefined set of messages that the relay and the device are expecting to exchange. π
The first mode is protocol mode, which lets a client interact with the relay β for example, join the relay, or request to connect to a device, given it is available on the relay. Similarly to BEP, protocol mode requires the device to connect via TLS using a strong suite of ciphers (same as BEP), which allows the relay to verify and derive the identity (Device ID) of the device. π
The second mode is session mode, which after a few initial messages connects two devices directly to each other via the relay, and is a plain-text protocol. For every byte written by one device, it sends the same set of bytes to the other device and vice versa. π«Ά
π΅οΈ IDENTIFYING THE CONNECTION MODE π
Because both connection modes operate over the same single socket, a method of detecting the connection mode is required. No cap. π
When a new client connects to the relay, the relay checks the first byte that the client has sent, and if that matches 0x16, that implies to us that the connection is a protocol mode connection, due to 0x16 being the first byte in the TLS handshake, and only protocol mode connections use TLS. π
If the first byte is not 0x16, then we assume that the connection is a session mode connection. Simple as that. β¨
π°οΈ PROTOCOL MODE π
Protocol mode uses TLS and the protocol name defined by the TLS header should be bep-relay. π‘
Protocol mode has two submodes:
- Permanent protocol submode β Joining the relay, and waiting for messages from the relay asking to connect to some device which is interested in having a session with you. βΎοΈ
- Temporary protocol submode β Only used to request a session with a device which is connected to the relay using the permanent protocol submode. β³
βΎοΈ Permanent protocol submode
A permanent protocol submode begins with the client sending a JoinRelayRequest message, which the relay responds to with either a ResponseSuccess or ResponseAlreadyConnected message if a client with the same device ID already exists. π«‘
After the client has joined, no more messages are exchanged apart from Ping/Pong messages for general connection keep alive checking. π
From this point onwards, the client stand-byβs and waits for SessionInvitation messages from the relay, which implies that some other device is trying to connect with you. The SessionInvitation message contains the unique session key which then can be used to establish a connection in session mode. π
If the client fails to send a JoinSessionRequest message within the first ping interval, the connection is terminated. If the client fails to send a message (even if itβs a ping message) every minute (by default), the connection is terminated. π
β³ Temporary protocol submode
A temporary protocol submode begins with a ConnectRequest message, to which the relay responds with ResponseNotFound if the device the client is after is not available, with a RelayFull if the relay has reached its limits, or with a SessionInvitation, which contains the unique session key which then can be used to establish a connection in session mode. πΈ
The connection is terminated immediately after that. π
π¨ Example Exchange
Client A - Permanent protocol submode Client B - Temporary protocol submode
ββββββ¬βββββββββββββββββββββ¬βββββββββββββββββββββββββ¬ββββββββββββββββββββββ
β # β Client (A) β Relay β Client (B) β
ββββββΌβββββββββββββββββββββΌβββββββββββββββββββββββββΌββββββββββββββββββββββ€
β 1 β JoinRelayRequest-> β β β
ββββββΌβββββββββββββββββββββΌβββββββββββββββββββββββββΌββββββββββββββββββββββ€
β 2 β β <-ResponseSuccess β β
ββββββΌβββββββββββββββββββββΌβββββββββββββββββββββββββΌββββββββββββββββββββββ€
β 3 β Ping-> β β β
ββββββΌβββββββββββββββββββββΌβββββββββββββββββββββββββΌββββββββββββββββββββββ€
β 4 β β <-Pong β β
ββββββΌβββββββββββββββββββββΌβββββββββββββββββββββββββΌββββββββββββββββββββββ€
β 5 β β β <-ConnectRequest(A) β
ββββββΌβββββββββββββββββββββΌβββββββββββββββββββββββββΌββββββββββββββββββββββ€
β 6 β β SessionInvitation(A)-> β β
ββββββΌβββββββββββββββββββββΌβββββββββββββββββββββββββΌββββββββββββββββββββββ€
β 7 β β <-SessionInvitation(B) β β
ββββββΌβββββββββββββββββββββΌβββββββββββββββββββββββββΌββββββββββββββββββββββ€
β 8 β β β (Disconnects) β
ββββββΌβββββββββββββββββββββΌβββββββββββββββββββββββββΌββββββββββββββββββββββ€
β 9 β Ping-> β β β
ββββββΌβββββββββββββββββββββΌβββββββββββββββββββββββββΌββββββββββββββββββββββ€
β 10 β β <-Pong β β
ββββββΌβββββββββββββββββββββΌβββββββββββββββββββββββββΌββββββββββββββββββββββ€
β 11 β Ping-> β β β
ββββββΌβββββββββββββββββββββΌβββββββββββββββββββββββββΌββββββββββββββββββββββ€
β 12 β β <-Pong β β
ββββββ΄βββββββββββββββββββββ΄βββββββββββββββββββββββββ΄ββββββββββββββββββββββ
πΈ SESSION MODE πΈ
Okay so bestie, in session mode, the first and only message the client sends is the JoinSessionRequest message β and it contains the session key that identifies which session you're trying to join π. The relay responds with one of the following messages:
ResponseNotFoundβ the session key is invalid πResponseAlreadyConnectedβ the session is full (both sides already connected) πResponseSuccessβ you have successfully joined the session πRelayFullβ the relay limits are too strict for you to be able to join the session. The relay immediately terminates the connection after sending this β οΈ
After the successful response, all the bytes written and received will be relayed between the two devices in the session directly π«Ά.
β¨ Example Exchange β¨
Client A is the first to join the session. Client B is the second to join the session. Both are in session mode.
Here's the exchange, step by step:
βββββ¬ββββββββββββββββββββββββββ¬ββββββββββββββββββββ¬ββββββββββββββββββββββββββ
β # β Client (A) β Relay β Client (B) β
βββββΌββββββββββββββββββββββββββΌββββββββββββββββββββΌββββββββββββββββββββββββββ€
β 1 β JoinSessionRequest(A)-> β β β
βββββΌββββββββββββββββββββββββββΌββββββββββββββββββββΌββββββββββββββββββββββββββ€
β 2 β β <-ResponseSuccess β β
βββββΌββββββββββββββββββββββββββΌββββββββββββββββββββΌββββββββββββββββββββββββββ€
β 3 β Data-> β (Buffers data) β β
βββββΌββββββββββββββββββββββββββΌββββββββββββββββββββΌββββββββββββββββββββββββββ€
β 4 β Data-> β (Buffers data) β β
βββββΌββββββββββββββββββββββββββΌββββββββββββββββββββΌββββββββββββββββββββββββββ€
β 5 β β β <-JoinSessionRequest(B) β
βββββΌββββββββββββββββββββββββββΌββββββββββββββββββββΌββββββββββββββββββββββββββ€
β 6 β β ResponseSuccess-> β β
βββββΌββββββββββββββββββββββββββΌββββββββββββββββββββΌββββββββββββββββββββββββββ€
β 7 β β Relays data -> β β
βββββΌββββββββββββββββββββββββββΌββββββββββββββββββββΌββββββββββββββββββββββββββ€
β 8 β β Relays data -> β β
βββββΌββββββββββββββββββββββββββΌββββββββββββββββββββΌββββββββββββββββββββββββββ€
β 9 β β <-Relays data β <-Data β
βββββ΄ββββββββββββββββββββββββββ΄ββββββββββββββββββββ΄ββββββββββββββββββββββββββ
π MESSAGES π
Okay bestie, every single message gets a header first β like a little name tag so you know what's coming. The header contains the magic value 0x9E79BC40, a message type integer, and the message length. Slay.
β οΈ WARNING: Some messages have no content, apart from the implied header which allows us to identify what type of message it is. They're literally just vibes and a type number, no cap.
π Header structure π
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Magic |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Message Type |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Message Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
struct Header {
unsigned int Magic;
int MessageType;
int MessageLength;
}
π Ping message (Type = 0) π
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
struct Ping {
}
π«Ά Pong message (Type = 1) π«Ά
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
struct Pong {
}
πͺ JoinRelayRequest message (Type = 2) πͺ
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
struct JoinRelayRequest {
}
π JoinSessionRequest message (Type = 3) π
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Length of Key |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/ /
\ Key (variable length) \
/ /
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
struct JoinSessionRequest {
opaque Key<32>;
}
Keyβ This is a unique random session key generated by the relay server. It is used to identify which session you are trying to connect to. It's giving "VIP wristband at the relay club" energy, honestly.
π¬ Response message (Type = 4) π¬
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Code |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Length of Message |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/ /
\ Message (variable length) \
/ /
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
struct Response {
int Code;
string Message<>;
}
Codeβ An integer representing the status code. The relay's little mood ring.Messageβ Message associated with the code. The relay's out-loud explanation of that mood.
And here are the exact response glow-ups, no notes:
ResponseSuccess = Response{0, "success"}
ResponseNotFound = Response{1, "not found"}
ResponseAlreadyConnected = Response{2, "already connected"}
ResponseInternalError = Response{99, "internal error"}
ResponseUnexpectedMessage = Response{100, "unexpected message"}
π ConnectRequest message (Type = 5) π
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Length of ID |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/ /
\ ID (variable length) \
/ /
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
struct ConnectRequest {
opaque ID<32>;
}
IDβ Device ID to which the client would like to connect. Like sliding into the right device's DMs, but with cryptography.
π SessionInvitation message (Type = 6) π
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Length of From |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/ /
\ From (variable length) \
/ /
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Length of Key |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/ /
\ Key (variable length) \
/ /
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Length of Address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/ /
\ Address (variable length) \
/ /
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 0x0000 | Port |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Server Socket (V=0 or 1) |V|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
struct SessionInvitation {
opaque From<32>;
opaque Key<32>;
opaque Address<32>;
unsigned int Port;
bool ServerSocket;
}
Fromβ Device ID identifying who you will be connecting with. The "you're meeting up with this one" field.Keyβ A unique random session key generated by the relay server. It is used to identify which session you are trying to connect to. Same iconic VIP wristband as before.Addressβ An optional IP address on which the relay server is expecting you to connect, in order to start a connection in session mode. Empty/all zero IP should be replaced with the relayβs public IP address that was used when establishing the protocol mode connection.Portβ The port on which the relay server is expecting you to connect, in order to start a connection in session mode.Server Socketβ Because both sides connecting to the relay use the client side of the socket, and some protocols behave differently depending if the connection starts on the server side or the client side, this boolean indicates which side of the connection this client should assume itβs getting. The value is inverted in the invitation which is sent to the other device, so that there is always one client socket, and one server socket. It's literally a "you be the server, I'll be the client" handshake, periodt.
π RelayFull message (Type = 7) π
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
struct RelayFull {
}
π‘οΈ HOW SYNCTHING USES RELAYS, AND GENERAL SECURITY π‘οΈ
Bestie, here's where it gets genuinely reassuring. In the case of Syncthing and BEP, when two devices connect via relay, they start their standard TLS connection encapsulated within the relayβs plain-text session connection β effectively upgrading the plain-text connection to a TLS connection. It's giving "we met at a public spot but we immediately stepped into a private booth."
Even though the relay could be used for man-in-the-middle attack, using TLS at the application/BEP level ensures that all the traffic is safely encrypted, and is completely meaningless to the relay. Like, the relay is carrying the bags but it has zero clue what's in them. Furthermore, the secure suite of ciphers used by BEP provides forward secrecy, meaning that even if the relay did capture all the traffic, and even if the attacker did get their hands on the device keys, they would still not be able to recover/decrypt any traffic which was transported via the relay. Forward secrecy ate and left no crumbs.
After establishing a relay session, Syncthing looks at the SessionInvitation message, and depending which side it has received, wraps the raw socket in either a TLS client socket or a TLS server socket depending on the ServerSocket boolean value in the SessionInvitation, and starts the TLS handshake. Main character energy from the handshake onward, honestly.
From that point onwards it functions exactly the same way as if Syncthing was establishing a direct connection with the other device over the internet, performing device ID validation, and full TLS encryption, and provides the same security properties as it would provide when connecting over the internet. So yeah β relayed, but still fully encrypted, fully validated, fully safe. π«Ά
β¨ EXAMPLES OF STRONG CIPHER SUITES β¨
okay bestie, this is the good-stuff list π these are the cipher suites you actually want on ur relay β the strong ones, the ones that make the vibe check pass with flying colors. Each one's got an ID, a Name, and a lil Description of what's going on under the hood. Slay.
0x009FβDHE-RSA-AES256-GCM-SHA384β TLSv1.2 DH RSA AESGCM(256) AEAD π«Ά0x006BβDHE-RSA-AES256-SHA256β TLSv1.2 DH RSA AES(256) SHA2560xC030βECDHE-RSA-AES256-GCM-SHA384β TLSv1.2 ECDH RSA AESGCM(256) AEAD π₯0xC028βECDHE-RSA-AES256-SHA384β TLSv1.2 ECDH RSA AES(256) SHA3840x009EβDHE-RSA-AES128-GCM-SHA256β TLSv1.2 DH RSA AESGCM(128) AEAD0x0067βDHE-RSA-AES128-SHA256β TLSv1.2 DH RSA AES(128) SHA2560xC02FβECDHE-RSA-AES128-GCM-SHA256β TLSv1.2 ECDH RSA AESGCM(128) AEAD β¨0xC027βECDHE-RSA-AES128-SHA256β TLSv1.2 ECDH RSA AES(128) SHA256
like, the AEAD girlies at the top? iconic. main character energy fr fr. the ones with bigger numbers aren't automatically better either β it's giving sophistication, not a size contest π
π AUTHOR π
The Syncthing Authors
(they ate and left zero crumbs, no cap π)
π COPYRIGHT π
2014-2026, The Syncthing Authors
okay that's literally the whole page, bestie!! π¦ you made it to the end, i'm so proud of you. go forth and relay responsibly β strong ciphers only, always read the fine print, and remember: the vibe check is eternal. mwah π
this lil masterpiece is from source/version v2.1.0, dated Oct 07, 2026 β take a screenshot, frame it, put it on the fridge π«Άβ¨
π¦ where this page comes from
- arch rolling: syncthing 2.1.7-1 Β· MPL-2.0
- nixos 26.05: syncthing 2.1.7
- nixpkgs unstable: syncthing 2.1.7
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms