Using Docker to Build Your Own STUN/TURN Server Based on Coturn
本文最后更新于 287 天前,其中的信息可能已经有所发展或是发生改变,如有失效可到评论区留言。
Article Abstract
To solve the needs of IPv6 port forwarding and intranet penetration, a Docker-based Coturn server is deployed to achieve STUN/TURN functions. The solution covers IPv4/IPv6 dual-stack environment configuration, adopts automatic port detection and domain name resolution strategies, and tests and verifies the penetration effects under different network scenarios. Actual tests show that STUN can achieve one-way communication hole punching, while TURN serves as a backup solution to ensure connection reliability. However, in practical applications, it needs to be combined with dynamic domain name resolution technology to stably obtain public IP addresses. The article also points out the advantages of mature solutions such as Tailscale in private network penetration, providing a technical extension direction for subsequent research.
Qwen3-14B · 2026-06-18

Preface

Actually, I've never been very interested in intranet penetration technology before, mainly because my home broadband is an IPv4/IPv6 public network dual-stack environment (IPv4 is even triple-dialed, with 3 public IPs and 3 60 Mbps uplinks—more than I could ever use!), once DDNS is configured and port forwarding is added on iKuai, I can access it however I want, and I haven't even really started using IPv6 yet.

I do know about stun, because Tailscale's DERP relay server also leaves port 3478 open by default. I'm generally clear on the principles; after all, when I was doing pre-sales, I also sold CGN products and had a rough understanding of these hole-punching technologies, but I had never put them into practice because Tailscale is just too convenient... It works perfectly fine, so why bother worrying about the principles...

This time, it was because I planned to study Lucky's IPv6 -> IPv4 address port forwarding function (since iKuai does not support IPv6 port forwarding. Although dynamic domain name solutions can also solve the problem of accessing hosts with different public IPv6 addresses in the intranet, from a security perspective, a unified entry point filtered by security devices is necessary), and I happened to see stun:

image.png

So I decided to look into it, treat it as a bit of scientific research, eliminate a blind spot in my knowledge, and write a casual article.

How stun and turn work

How do stun and turn work?

If we want to explain this clearly from a purely technical perspective, we would first have to explain how NAT works, then the classification of NAT (Full Cone NAT, Address Restricted Cone NAT, Port Restricted Cone NAT, Symmetric NAT), then reverse connection technology, TCP/UDP hole punching technology... Expanding on all of that would be too much, and I think most friends wouldn't be very interested, plus there are already many such articles online. So I don't plan to explain it that way. I'll just summarize it simply in my own words. However, there will definitely be inaccuracies in the technical details, as it can only be considered an analogy.

Depending on whether the accessing client and the access target have public IP addresses, our access can be divided into the following types.
Both the accessing client and the access target have public IP addresses

image.png

This situation is equivalent to both parties having mobile phone numbers, making it very easy to find each other. Not only can the accessing client directly find the access target, but conversely, the access target can also directly find the accessing client. This is called two-way communication.
The accessing client does not have a public IP address, while the access target has a public IP address

image.png

In this case, it just means the access target cannot find the accessing client, since there is only a main landline number. However, the accessing client can still directly find the access target. This is called one-way communication, and the vast majority of access nowadays is of this type.
The accessing client has a public IP address, while the access target does not have a public IP address (but hole punching can be performed)

image.png

This situation is more troublesome. Although the accessing client has a mobile phone number, the access target only has a main number and no extension number. What to do? This is where stun makes its grand entrance:

image.png

In this case, stun can be understood as a middleman or a proxy. He knows both the accessing client and the access target and can contact them directly (assuming stun has the mobile numbers of both parties, but the access target's mobile number cannot be directly given to the accessing client. This is understandable; nowadays, many clients are unwilling to directly give their mobile numbers to contractors, leaving only work numbers). When the accessing client wants to communicate with the access target: Step 1, contact stun to ask for the specific extension number of the access target; Step 2, stun will directly contact the access target to ask for the extension number; Step 3, the access target tells stun the extension number; Step 4, stun tells the accessing client the access target's extension number; Step 5, the accessing client directly contacts the access target using the main number + extension number.
Note 1: The premise of this method is that the access target has an extension number and can tell others the extension number (confidential units cannot leak extension numbers). In technical terms, the NAT type supports hole punching, and at least it is not Symmetric NAT.
Note 2: Assuming the accessing client's mobile number is directly given to the access target, allowing the access target to directly call the accessing client using the company landline, this is called a reverse connection. However, this is not how stun operates.
Note 3: In this method, stun is only responsible for matchmaking. It does not care about the details of the subsequent communication between the accessing client and the access target. In technical terms, the subsequent access traffic has nothing to do with stun. For example, watching movies or transferring large files are peer-to-peer between the accessing client and the access target, and the speed depends on the upstream bandwidth of the access target.
Note 4: This is actually how the so-called high-bandwidth, unlimited-traffic intranet penetration on the internet is implemented.
Neither the accessing client nor the access target has a public IP address, and hole punching is impossible

This can be understood as the unit where the access target is located being a confidential unit that cannot leak extension numbers, and the accessing client has no mobile phone either, only a main number. Neither can access the other. What to do in this situation? turn finally makes its grand entrance:

image.png

In this case, it can be understood that because the accessing client and the access target cannot contact each other directly, turn says: "I am a megaphone. Since you both trust me, all communication will be conveyed by me. Even if it's sending gifts (large files), leave it to me to deliver."

Compared to stun, turn is considered a last resort. The advantage is that establishing a connection is definitely not a problem, but the disadvantage is that the speed is limited by the upstream and downstream bandwidth of the turn server. Therefore, stun is usually used first, and turn is used if that fails.

In fact, Tailscale's DERP relay server also uses the stun protocol, but in addition to stun, there are many other technologies, so the actual experience with Tailscale is much better. For building a DERP server, see:Debian Series: Building a Tailscale DERP Server (Relay Server) for Dummies。

Note 1: Building stun and turn both require the deployed host to have a public IP address, and also require the accessing end and the access target to be UDP-reachable to the deployed host.

Note 2: You can download a NAT type testing tool for Windows to test the NAT type of your own environment. The download link is as follows:https://github.com/HMBSbige/NatTypeTester/releases。
Select RFC 5780:

image.png

image.png

Deploying coturn

Note: There are also many ways to deploy coturn. I chose the Docker method because it is the easiest to deploy, but this may not be the best-performing method. You can choose the deployment method that best suits your actual environment.

The deployed host has a public IPv4 address

My Tencent Cloud Lightweight Application Server has a fixed public IPv4 address (although it seems some cloud hosts nowadays do not necessarily provide a fixed public IPv4 address directly, and the so-called fixed public IP of cloud hosts is actually just a one-to-one mapping, with the server itself still having a private address), and my home broadband has a dynamic public IPv4 address (pointing to the intranet host through the router's port mapping function). In order to obtain them in different scenarios,external IP和relay IPFor these two parameters required by coturn, it is recommended to use coturn's automatic detection method.
The command required to set up Docker is as follows:

docker run --name coturn -d --restart=always \
   -e DETECT_EXTERNAL_IP=yes \ #自动检测external-ip
   -e DETECT_RELAY_IP=yes \ #自动检测relay-ip
   -e user=guest:guest \ #这是turn用于认证的用户名和密码,这里都为guest,因为turn是需要干重活的,stun就动动嘴皮子,所以没必要认证。
   -e realm=stun.example.com \ #填写公网IPv4地址对应的域名(也可以是动态域名)
   -p 3478:3478/tcp -p 3478:3478/udp \ # 指定stun和turn的监听端口,默认3478,也可以改成其他的,这个到无所谓,因为stun的客户端在填写服务器端地址时能够指定端口号,我就没用默认端口
   -p 5349:5349/tcp -p 5349:5349/udp \ #stun和turn的tls监听端口,实际上tls会话也能连接3478端口,同时保持3478和5349是为了满足RFC 5766规范,这个端口不映射貌似也可以
   -p 49160-49170:49160-49170/udp \ #这些端口是用来在turn模式下交换媒体数据的,端口范围自行指定即可,一般没啥大需要范围填小点
   coturn/coturn --min-port=49160 --max-port=49170 #指定最小到最大的端口范围

-e DETECT_EXTERNAL_IP=yes \ # Automatically detect external-ip-e DETECT_RELAY_IP=yes \ # Automatically detect relay-ip-e user=guest:guest \ # This is the username and password used by turn for authentication, both are guest here. Since turn does the heavy lifting while stun just talks, there is no need for authentication.-p-e realm=stun.example.com \ # Fill in the domain name corresponding to the public IPv4 address (can also be a dynamic domain name)

-p 3478:3478/tcp -p 3478:3478/udp \ # Specify the listening ports for stun and turn, default is 3478, can also be changed to others, which doesn't really matter because the stun client can specify the port number when filling in the server address, so I didn't use the default port

The deployed host has a public IPv6 address

-p 5349:5349/tcp -p 5349:5349/udp \ # TLS listening ports for stun and turn. Actually, TLS sessions can also connect to port 3478. Keeping both 3478 and 5349 is to satisfy the RFC 5766 specification; it seems fine not to map this port-p 49160-49170:49160-49170/udp \ # These ports are used to exchange media data in turn mode. You can specify the port range yourself; generally, if there is no major need, keep the range smallIt can detect both IPv4 and IPv6 addresses, so the commands in the previous section do not need to be changed. Instead, a key issue should be the AAAA resolution of the domain name stun.example.com, which needs to be dynamically resolved to the IPv6 address corresponding to the deployment host. At the same time, the STUN client must also have an IPv6 address and use the stun.example.com domain name as the connection address for the STUN server, and be able to correctly receive the AAAA record resolution corresponding to the domain name. This requirement exists because some DNS servers or DNS proxies can disable the resolution of AAAA records, such as iKuai:

image.png

Testing

Scientific and comprehensive testing methods

After the deployment is complete, you can use the following link to test:Test link。
The test interface is as follows:

image.png

Using Tencent Cloud Lightweight Application Server as a stun server for testing

The figure below shows the results of my setup and testing using a Tencent Cloud server (fixed public IPv4 address):

image.png

Using home broadband devices as a stun server for testing

The figure below shows the results of my setup and testing using my home devices:
Enable both IPv4 and IPv6 resolution for the access domain name:

image.png

Wow, a whole bunch of nodes were detected. This is related to my complex home environment, which includes IPv4, IPv6, Tailscale virtual addresses, etc. Moreover, my test STUN client and my STUN server are actually in the same local area network (utilizing iKuai's port loopback function, which is actually hairpinning). Crucially, the access domain name of my STUN server supports both A record and AAAA record resolution simultaneously, so I can't even make sense of these nodes... But that doesn't matter, the result is a pass. The key point is: there are 2 srflx and 2 relay nodes, which shows that if both IPv4 and IPv6 resolutions are available, and the STUN client also supports a dual-stack IPv4 and IPv6 environment, they can be detected simultaneously.
Enable only IPv4 resolution for the access domain name:

image.png

This is much more normal.
Enable only IPv6 resolution for the access domain name:
image.png

Note: Because I used an extremely non-standard environment where the STUN server and STUN client are deployed in the same local network (and also have IPv4 addresses) to simulate an IPv6 environment, the test results are a bit strange. However, it doesn't matter, as long as it proves that STUN can still work when the access domain name resolution only has an IPv6 address.

Testing methods using a stun client

If you already have an existing STUN client, you can test its usage directly. Taking the STUN client that comes with Lucky as an example:

image.png

image.png

image.png

After waiting for a while, the address and port in the red box will appear at the STUN penetration public address, indicating success:
image.png

Access using the obtained public address and port:
image.png

Summary

Actually, at this point, the setup of the STUN server and the experimental use with the STUN client have been completed. However, it can be seen that this actually has little practical value. Why?

image.png

Because the public IP and port corresponding to the "STUN penetrated public address" in the red box above are constantly changing, how can you use it? So if you really want it to be practical, there is still one step left, which is to convert the changing public IP and port in the red box into a constant domain name, just like the dynamic domain names we usually use. However, this special "dynamic domain name" needs to be implemented through the API interface of your domain name provider and at least two variables of your account in the domain name provider's console (such as domain ID, record ID, or zone ID, rule ID, etc.), followed by a series of operations. I looked into it, and the configuration method for each domain name provider is different, with little in common, so writing one doesn't make much sense (mainly because I'm too lazy to do it, it was originally just a research topic, plus STUN is essentially suitable for use as an underlying technology, for example, Syncthing relies on STUN to work, you can refer to my other article:Docker Series: Detailed Tutorial on Implementing Multi-Point Folder Synchronization Based on Syncthing Using Docker), so I'll stop here. That's all, I hope this article is helpful to friends who need STUN.

P.S.: Highly recommend Tailscale, isn't a fixed private IP great?

📌 Content Structure Prompt:
This content belongs to the "Blog Knowledge Map" part, you can view the complete content path from here: Blog Knowledge Map 。
View Related Categories · 3 Matches
📎 Related Articles
Share this article
The blog content is original, please indicate the source when reposting! The RSS address of the blog is:https://blog.tangwudi.com/feed, welcome to subscribe; if needed, you can join theTelegram Groupto discuss questions together.
No Comments

Send Comment Edit Comment


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗ
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
Kaomoji
Emoji
Little Dinosaur
Flower!
Previous
Next
       

👋 Welcome to “Wudi's Personal Blog”

Here, long-term exploration is mainly carried out around the following directions:

🧱 Personal Digital Infrastructure and Blog System Construction
☁️ Cloudflare and Network Architecture Practice
🧠 AI and Knowledge System Exploration
🛡️ Network Security and Access Optimization
🎵 Music and Sound Cognition
👁️ Cognitive Perspectives and Worldviews