Pion DTLS: a native DTLS 1.2 implementation in Go, with DTLS 1.3 in progress
DTLS 1.2 implementation for Go (DTLS 1.3 in progress). Pion DTLS A Go implementation of DTLS Native [DTLS 1.2][rfc6347] implementation in the Go programming language.
At a glance
- What is it?
- Pion DTLS gives Go programs a DTLS client and server without cgo or a C library underneath. The README documents DTLS 1.2 as the shipping protocol, lists DTLS 1.3 as work in progress, and freezes the main branch from tagging until that work lands.
- Who is it for?
- Adopt Pion DTLS if you are writing a Go service that terminates DTLS itself, for example a WebRTC stack, a CoAP or VPN endpoint, or a PSK-protected sensor gateway, and you want the handshake inside your own process rather than in an OpenSSL binding.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 3 days ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 26, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What Pion DTLS replaces, and who ends up depending on it
DTLS is TLS adapted to datagram transports, so a handshake has to tolerate packets that arrive out of order or not at all. In Go, the usual way to get that has been cgo bindings to OpenSSL, which pulls a C toolchain, a system library version, and a build story into what is otherwise a static binary. Pion DTLS is a native implementation: the README describes it as a "Native DTLS 1.2 and DTLS 1.3 implementation in the Go programming language", and the repository is a Go module with Go source files at the top level (conn.go, listener.go, session.go, resume.go) rather than a wrapper around a C library. The direct audience is Go developers who need to terminate or originate DTLS inside their own process. The indirect audience is larger: Pion DTLS is part of the Pion family, and the related searches around Pion ICE and Pion SIP suggest people arrive here while assembling a WebRTC or real-time stack rather than looking for DTLS on its own. That matters for how you read the project. It is a building block, not an application, and its API is shaped for embedding.
Handshake state, retransmission and the connection identifier
The mechanism is a state machine over UDP. The repository layout shows the split: conn.go holds the connection, flight_test.go exercises handshake flights, handshaker_test.go drives the handshake itself, resume.go and session.go handle session serialization, and connection_id.go implements the connection identifier extension. The README states that packet loss and re-ordering is handled during handshaking, which is the part of DTLS that differs most from TLS: each flight of handshake messages must be retransmitted on a timer until the peer responds, and the implementation has to keep enough state to accept a flight that arrives late. Two RFCs in the implemented list are worth calling out because they change what the connection looks like on the wire. RFC 9146 adds a connection identifier to DTLS 1.2, and RFC 9853 adds a return routability check for DTLS 1.2 and 1.3. Both exist to keep a session alive when the peer's address changes, which is the normal condition for mobile clients, and both are the kind of thing you only notice when they are missing. Key exchange is ECDHE over curve25519, nistp256 or nistp384, or PSK, and the supported cipher list is long enough to be worth reading before you assume interop: DTLS 1.2 ECDHE suites include the AES-CCM, AES-GCM and ChaCha20-Poly1305 families, the PSK list includes TLS_PSK_WITH_AES_128_CCM_8 and TLS_PSK_WITH_CHACHA20_POLY1305_SHA256, and there is one combined suite, TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256.
Installing Pion DTLS and running the selfsign pair
The module path is github.com/pion/dtls/v3, and go.mod declares go 1.24.0, so the toolchain needs to be at least that. Adding it is a normal module fetch.
go get github.com/pion/dtls/v3The README does not walk through an API example in prose. What it gives instead is a pair of runnable programs under examples/, and that is the fastest way to see a handshake complete. One process listens on 127.0.0.1:4444, the other dials it.
go run examples/listen/selfsign/main.goIn a second terminal, run the client. If the handshake succeeds the client connects and the programs exchange application data.
go run examples/dial/selfsign/main.goThe PSK variant swaps the certificate for a pre-shared key and lives in a parallel directory, so the same two commands with psk in place of selfsign exercise a different key exchange path.
go run examples/listen/psk/main.go
go run examples/dial/psk/main.goIf you want to prove interop rather than self-consistency, the README documents an OpenSSL path. Generate a P-256 key and a self-signed certificate, then point openssl s_server at the Pion dial example on port 4444 with -dtls1_2, or point openssl s_client at the Pion listener. For PSK the README uses -psk abc123 with -cipher PSK-AES128-CCM8, which lines up with the TLS_PSK_WITH_AES_128_CCM_8 suite in the supported list.
What is deliberately missing, and the retracted versions
The excluded features list is short and unambiguous: DTLS 1.0, renegotiation, and compression. If you are talking to an old embedded peer that only speaks DTLS 1.0, this library is the wrong tool and no configuration flag will fix that. Renegotiation being absent is a design decision as much as a gap; it is also a feature with a long history of implementation bugs elsewhere, so its absence is defensible, but it means a long-lived connection cannot rotate its parameters in place. The go.mod file carries two retract directives that deserve more attention than they usually get. Version v3.1.0 is retracted for "broken RSA interop with OpenSSL DTLS 1.2", and v3.1.3 is retracted for "broken interoperability with firefox". The Go toolchain honours retract directives when resolving versions, but if you have pinned either of those in a lockfile or vendored them, you are running a build the maintainers have publicly disowned. This is also the clearest signal about the project's testing surface: interop with OpenSSL and with Firefox is tested closely enough that a regression produced a retraction rather than a quiet patch. The README does not document a rollback procedure, and it does not state a support window for the v3 line.
The frozen main branch and what it means for upgrades
The README carries a warning that the branch is "currently frozen from tagging while DTLS 1.3 work is underway". The instruction that follows is specific: DTLS 1.3 and breaking changes target main, while bug fixes and DTLS 1.2 improvements target the v3 branch, which can be used for tags until DTLS 1.3 is ready. Read that as a maintenance contract with two lanes. If you depend on DTLS 1.2, your fixes arrive on v3, and the recent releases in that lane are patch-level (v3.1.6, v3.1.7, v3.1.8). If you want DTLS 1.3, you are on main, which is where breaking changes are allowed to land, so a go get against main can move under you. The last push to the repository was on 2026-08-29, the same day as v3.1.8. For a library like this, the upgrade cost is mostly about cipher and extension surface rather than API churn: adding a suite or an RFC changes what your peer can negotiate, and the retractions show that interop regressions are the failure mode to watch. The licence is MIT, which the repository states in LICENSE with a LICENSES/ directory alongside it, and the README notes that the DTLS 1.3 work is funded through the NGI0 Commons Fund under grant agreement 101135429. MIT is permissive, but a funded workstream is not the same as a commercial support contract; the README points commercial enquiries at [email protected].
Pion DTLS against using OpenSSL through bindings
The real alternative for most Go teams is not another Go library, it is the cgo route: bind OpenSSL and call its DTLS functions. The difference in approach is where the protocol state lives. With OpenSSL bindings, the handshake state lives in a C context, and your Go code marshals buffers in and out of it; you inherit OpenSSL's cipher coverage and its FIPS story, and you inherit its build requirements on every platform you ship to. With Pion DTLS, the handshake is Go code in your process, the memory is garbage collected, cross-compilation is the same as any other Go build, and you can read conn.go when something behaves oddly. The cost is coverage: OpenSSL implements far more of the DTLS surface, including the DTLS 1.0 and renegotiation paths Pion lists as excluded, and it has had far more adversarial attention. The README acknowledges this directly, calling a professional security review a long term goal and mentioning possible inclusion in stdlib. Until that review happens, the honest comparison is that Pion DTLS buys you a simpler build and a readable implementation, and OpenSSL buys you breadth and a longer track record.
Who should adopt Pion DTLS, and what to check first
Adopt it when the DTLS endpoint belongs inside your Go service and the peer set is one you control or can enumerate. A WebRTC media path, a CoAP gateway, a VPN endpoint, or a PSK-protected fleet of sensors all fit: the PSK examples exist precisely because certificate management on constrained devices is painful. The connection identifier and return routability work in the implemented RFC list is aimed at the same audience, clients whose address changes mid-session. Do not adopt it if you need DTLS 1.0, renegotiation, or compression, and be careful if you need a tagged DTLS 1.3 release right now, because the README says the branch is frozen from tagging while that work is underway. Three things to verify before you commit. First, grep your dependency graph for v3.1.0 and v3.1.3, both retracted. Second, confirm the exact cipher suite your peer will offer appears in the supported list, since the DTLS 1.2 ECDHE and PSK lists are separate and the combined ECDHE-PSK list has exactly one entry. Third, run the selfsign examples, then repeat against openssl s_server -dtls1_2 and openssl s_client -dtls1_2, because self-consistency proves far less than interop here.
Editorial conclusion
Adopt Pion DTLS if you are writing a Go service that terminates DTLS itself, for example a WebRTC stack, a CoAP or VPN endpoint, or a PSK-protected sensor gateway, and you want the handshake inside your own process rather than in an OpenSSL binding. Do not adopt it if you need DTLS 1.0, renegotiation, or compression, all of which the README lists as excluded, or if you need a tagged DTLS 1.3 release today, since the main branch is frozen from tagging while that work continues. Before committing, check the retract directives in go.mod, confirm the cipher suite your peer requires appears in the supported list, and run the selfsign listen and dial examples against each other and then against openssl s_server -dtls1_2.
Frequently asked questions
What does DTLS mean in Pion DTLS?
DTLS stands for Datagram Transport Layer Security, the version of TLS that runs over datagram transports such as UDP. Pion DTLS implements DTLS 1.2 per RFC 6347, with DTLS 1.3 per RFC 9147 in progress.
What does DTLS stand for?
It stands for Datagram Transport Layer Security. The README links the DTLS 1.2 specification as RFC 6347 and the DTLS 1.3 specification as RFC 9147.
Is DTLS better than TLS?
They solve different transport problems rather than one being better. TLS assumes a reliable, ordered stream, while DTLS is built for datagrams, and the README notes that packet loss and re-ordering is handled during handshaking in Pion DTLS.
How does the DTLS handshake work in Pion DTLS?
The handshake is driven by a state machine in Go, with flights of handshake messages retransmitted on a timer so that lost or reordered datagrams do not stall the exchange. The repository splits this across handshaker_test.go, flight_test.go and conn.go.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/pion-dtls)