1 { config, lib, pkgs, ... }:
3 cfg = config.services.coturn;
4 pidfile = "/run/turnserver/turnserver.pid";
5 configFile = pkgs.writeText "turnserver.conf" ''
6 listening-port=${toString cfg.listening-port}
7 tls-listening-port=${toString cfg.tls-listening-port}
8 alt-listening-port=${toString cfg.alt-listening-port}
9 alt-tls-listening-port=${toString cfg.alt-tls-listening-port}
10 ${lib.concatStringsSep "\n" (map (x: "listening-ip=${x}") cfg.listening-ips)}
11 ${lib.concatStringsSep "\n" (map (x: "relay-ip=${x}") cfg.relay-ips)}
12 min-port=${toString cfg.min-port}
13 max-port=${toString cfg.max-port}
14 ${lib.optionalString cfg.lt-cred-mech "lt-cred-mech"}
15 ${lib.optionalString cfg.no-auth "no-auth"}
16 ${lib.optionalString cfg.use-auth-secret "use-auth-secret"}
17 ${lib.optionalString (cfg.static-auth-secret != null) ("static-auth-secret=${cfg.static-auth-secret}")}
18 ${lib.optionalString (cfg.static-auth-secret-file != null) ("static-auth-secret=#static-auth-secret#")}
20 ${lib.optionalString cfg.no-udp "no-udp"}
21 ${lib.optionalString cfg.no-tcp "no-tcp"}
22 ${lib.optionalString cfg.no-tls "no-tls"}
23 ${lib.optionalString cfg.no-dtls "no-dtls"}
24 ${lib.optionalString cfg.no-udp-relay "no-udp-relay"}
25 ${lib.optionalString cfg.no-tcp-relay "no-tcp-relay"}
26 ${lib.optionalString (cfg.cert != null) "cert=${cfg.cert}"}
27 ${lib.optionalString (cfg.pkey != null) "pkey=${cfg.pkey}"}
28 ${lib.optionalString (cfg.dh-file != null) ("dh-file=${cfg.dh-file}")}
32 ${lib.optionalString cfg.secure-stun "secure-stun"}
33 ${lib.optionalString cfg.no-cli "no-cli"}
35 cli-port=${toString cfg.cli-port}
36 ${lib.optionalString (cfg.cli-password != null) ("cli-password=${cfg.cli-password}")}
42 enable = lib.mkEnableOption "coturn TURN server";
43 listening-port = lib.mkOption {
47 TURN listener port for UDP and TCP.
48 Note: actually, TLS and DTLS sessions can connect to the
49 "plain" TCP and UDP port(s), too - if allowed by configuration.
52 tls-listening-port = lib.mkOption {
56 TURN listener port for TLS.
57 Note: actually, "plain" TCP and UDP sessions can connect to the TLS and
58 DTLS port(s), too - if allowed by configuration. The TURN server
59 "automatically" recognizes the type of traffic. Actually, two listening
60 endpoints (the "plain" one and the "tls" one) are equivalent in terms of
61 functionality; but we keep both endpoints to satisfy the RFC 5766 specs.
62 For secure TCP connections, we currently support SSL version 3 and
63 TLS version 1.0, 1.1 and 1.2.
64 For secure UDP connections, we support DTLS version 1.
67 alt-listening-port = lib.mkOption {
69 default = cfg.listening-port + 1;
70 defaultText = lib.literalExpression "listening-port + 1";
72 Alternative listening port for UDP and TCP listeners;
73 default (or zero) value means "listening port plus one".
74 This is needed for RFC 5780 support
75 (STUN extension specs, NAT behavior discovery). The TURN Server
76 supports RFC 5780 only if it is started with more than one
77 listening IP address of the same family (IPv4 or IPv6).
78 RFC 5780 is supported only by UDP protocol, other protocols
79 are listening to that endpoint only for "symmetry".
82 alt-tls-listening-port = lib.mkOption {
84 default = cfg.tls-listening-port + 1;
85 defaultText = lib.literalExpression "tls-listening-port + 1";
87 Alternative listening port for TLS and DTLS protocols.
90 listening-ips = lib.mkOption {
91 type = lib.types.listOf lib.types.str;
93 example = [ "203.0.113.42" "2001:DB8::42" ];
95 Listener IP addresses of relay server.
96 If no IP(s) specified in the config file or in the command line options,
97 then all IPv4 and IPv6 system IPs will be used for listening.
100 relay-ips = lib.mkOption {
101 type = lib.types.listOf lib.types.str;
103 example = [ "203.0.113.42" "2001:DB8::42" ];
105 Relay address (the local IP address that will be used to relay the
106 packets to the peer).
107 Multiple relay addresses may be used.
108 The same IP(s) can be used as both listening IP(s) and relay IP(s).
110 If no relay IP(s) specified, then the turnserver will apply the default
111 policy: it will decide itself which relay addresses to be used, and it
112 will always be using the client socket IP address as the relay IP address
113 of the TURN session (if the requested relay address family is the same
114 as the family of the client socket).
117 min-port = lib.mkOption {
118 type = lib.types.int;
121 Lower bound of UDP relay endpoints
124 max-port = lib.mkOption {
125 type = lib.types.int;
128 Upper bound of UDP relay endpoints
131 lt-cred-mech = lib.mkOption {
132 type = lib.types.bool;
135 Use long-term credential mechanism.
138 no-auth = lib.mkOption {
139 type = lib.types.bool;
142 This option is opposite to lt-cred-mech.
143 (TURN Server with no-auth option allows anonymous access).
144 If neither option is defined, and no users are defined,
145 then no-auth is default. If at least one user is defined,
146 in this file or in command line or in usersdb file, then
147 lt-cred-mech is default.
150 use-auth-secret = lib.mkOption {
151 type = lib.types.bool;
155 Flag that sets a special authorization option that is based upon authentication secret.
156 This feature can be used with the long-term authentication mechanism, only.
157 This feature purpose is to support "TURN Server REST API", see
158 "TURN REST API" link in the project's page
159 https://github.com/coturn/coturn/
161 This option is used with timestamp:
163 usercombo -> "timestamp:userid"
164 turn user -> usercombo
165 turn password -> base64(hmac(secret key, usercombo))
167 This allows TURN credentials to be accounted for a specific user id.
168 If you don't have a suitable id, the timestamp alone can be used.
169 This option is just turning on secret-based authentication.
170 The actual value of the secret is defined either by option static-auth-secret,
171 or can be found in the turn_secret table in the database.
174 static-auth-secret = lib.mkOption {
175 type = lib.types.nullOr lib.types.str;
178 'Static' authentication secret value (a string) for TURN REST API only.
179 If not set, then the turn server
180 will try to use the 'dynamic' value in turn_secret table
181 in user database (if present). The database-stored value can be changed on-the-fly
182 by a separate program, so this is why that other mode is 'dynamic'.
185 static-auth-secret-file = lib.mkOption {
186 type = lib.types.nullOr lib.types.str;
189 Path to the file containing the static authentication secret.
192 realm = lib.mkOption {
193 type = lib.types.str;
194 default = config.networking.hostName;
195 defaultText = lib.literalExpression "config.networking.hostName";
196 example = "example.com";
198 The default realm to be used for the users when no explicit
199 origin/realm relationship was found in the database, or if the TURN
200 server is not using any database (just the commands-line settings
201 and the userdb file). Must be used with long-term credentials
202 mechanism or with TURN REST API.
205 cert = lib.mkOption {
206 type = lib.types.nullOr lib.types.str;
208 example = "/var/lib/acme/example.com/fullchain.pem";
210 Certificate file in PEM format.
213 pkey = lib.mkOption {
214 type = lib.types.nullOr lib.types.str;
216 example = "/var/lib/acme/example.com/key.pem";
218 Private key file in PEM format.
221 dh-file = lib.mkOption {
222 type = lib.types.nullOr lib.types.str;
225 Use custom DH TLS key, stored in PEM format in the file.
228 secure-stun = lib.mkOption {
229 type = lib.types.bool;
232 Require authentication of the STUN Binding request.
233 By default, the clients are allowed anonymous access to the STUN Binding functionality.
236 no-cli = lib.mkOption {
237 type = lib.types.bool;
240 Turn OFF the CLI support.
243 cli-ip = lib.mkOption {
244 type = lib.types.str;
245 default = "127.0.0.1";
247 Local system IP address to be used for CLI server endpoint.
250 cli-port = lib.mkOption {
251 type = lib.types.int;
257 cli-password = lib.mkOption {
258 type = lib.types.nullOr lib.types.str;
262 For the security reasons, it is recommended to use the encrypted
263 for of the password (see the -P command in the turnadmin utility).
266 no-udp = lib.mkOption {
267 type = lib.types.bool;
269 description = "Disable UDP client listener";
271 no-tcp = lib.mkOption {
272 type = lib.types.bool;
274 description = "Disable TCP client listener";
276 no-tls = lib.mkOption {
277 type = lib.types.bool;
279 description = "Disable TLS client listener";
281 no-dtls = lib.mkOption {
282 type = lib.types.bool;
284 description = "Disable DTLS client listener";
286 no-udp-relay = lib.mkOption {
287 type = lib.types.bool;
289 description = "Disable UDP relay endpoints";
291 no-tcp-relay = lib.mkOption {
292 type = lib.types.bool;
294 description = "Disable TCP relay endpoints";
296 extraConfig = lib.mkOption {
297 type = lib.types.lines;
299 description = "Additional configuration options";
304 config = lib.mkIf cfg.enable (lib.mkMerge ([
306 { assertion = cfg.static-auth-secret != null -> cfg.static-auth-secret-file == null ;
307 message = "static-auth-secret and static-auth-secret-file cannot be set at the same time";
312 users.users.turnserver =
313 { uid = config.ids.uids.turnserver;
314 group = "turnserver";
315 description = "coturn TURN server user";
317 users.groups.turnserver =
318 { gid = config.ids.gids.turnserver;
319 members = [ "turnserver" ];
322 systemd.services.coturn = let
323 runConfig = "/run/coturn/turnserver.cfg";
325 description = "coturn TURN server";
326 after = [ "network-online.target" ];
327 wants = [ "network-online.target" ];
328 wantedBy = [ "multi-user.target" ];
331 Documentation = "man:coturn(1) man:turnadmin(1) man:turnserver(1)";
335 cat ${configFile} > ${runConfig}
336 ${lib.optionalString (cfg.static-auth-secret-file != null) ''
337 ${pkgs.replace-secret}/bin/replace-secret \
338 "#static-auth-secret#" \
339 ${cfg.static-auth-secret-file} \
342 chmod 640 ${runConfig}
346 ExecStart = "${pkgs.coturn}/bin/turnserver -c ${runConfig}";
347 RuntimeDirectory = "turnserver";
349 Group = "turnserver";
350 AmbientCapabilities =
352 cfg.listening-port < 1024 ||
353 cfg.alt-listening-port < 1024 ||
354 cfg.tls-listening-port < 1024 ||
355 cfg.alt-tls-listening-port < 1024 ||
357 ) "cap_net_bind_service";
358 Restart = "on-abort";
361 systemd.tmpfiles.rules = [
362 "d /run/coturn 0700 turnserver turnserver - -"