Receiving RTMPS stream on NGINX-RTMP

UPDATE: This is my original answer that describes pretty well the issues one may face while implementing RTMPS with Nginx. However, I've added an improved version for more fine-tuned access control, and I recommend using the configuration from it, instead.


Yes, this is possible with stunnel, as RTMPS is just a RTMP session wrapped inside a standard TLS session. The examples on the Internet are mostly RTMP→RTMPS i.e. the stunnel is working as a plain text server and TLS client, which is configured with client = yes. Without that, the client defaults to no, which is the server mode.

The stunnel configuration could look like this:

[rtmps]
accept = 1935
connect = 127.0.0.1:1936
cert=/etc/letsencrypt/live/rtmp.example.com/fullchain.pem
key=/etc/letsencrypt/live/rtmp.example.com/privkey.pem

With this:

  • The Nginx should be listening for RTMP on local loopback, port 1936/tcp.
  • As you can't renew the Let's Encrypt ertificate using RTMP, you might need a HTTP server block for the HTTP-01 challenge, too.
  • As the connection to Nginx always comes from the stunnel i.e. from the 127.0.0.1, you can't use the allow/deny directives to limit connection based on IP addresses anymore. This means your access control would be limited to the key, alone, but at the same time it's less of a problem, as it's transmitted encrypted.

    However, this still causes problems, as you'd push the stream from the same IP than the clients that are using it, but you can't allow them to publish to your stream. Luckily, you don't have to push the stream from the application with the key, but you can also pull it from the public application (/live).

The following Nginx example configuration takes these considerations into account:

rtmp {
    server {
        listen 127.0.0.1:1936;
        chunk_size 4096;

        application app-secret-stream-key {
            live on;
            record off;
            allow publish 127.0.0.1;  # for streaming through stunnel
            allow play 127.0.0.1;     # for the pull from /live
        }

        application live {
            live on;
            record off;
            deny publish all;         # no need to publish on /live
            allow play all;           # playing allowed

            pull rtmp://127.0.0.1:1936/app-secret-stream-key;
        }
    }
}

http {
    server {
        listen 80;
        server_name rtmp.example.com;

        location ^~ /.well-known/acme-challenge/ {
            root /var/www/letsencrypt;
        }
        location / {
            return 404;
        }
    }
}

However, this is just an example, so you can and should modify it to fit your exact needs. (Also, I haven't tested this configuration but written it solely based on the documentation, so please feel free to correct, if I got something wrong.)


Since NGINX is capable of terminating TLS for upstream TCP servers, this should take care of it, using only NGINX (simply added stream section to config from @Esa Jokinen):

stream {
    upstream backend {
        server 127.0.0.1:1936;
    }
    server {
        listen 1935 ssl;
        proxy_pass backend;
        ssl_certificate /etc/letsencrypt/live/rtmp.example.com/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/rtmp.example.com/privkey.pem;
    }
}

rtmp {
    server {
        listen 127.0.0.1:1936;
        chunk_size 4096;

        application app-secret-stream-key {
            live on;
            record off;
            allow publish 127.0.0.1;  # for streaming through stunnel
            allow play 127.0.0.1;     # for the pull from /live
        }

        application live {
            live on;
            record off;
            deny publish all;         # no need to publish on /live
            allow play all;           # playing allowed

            pull rtmp://127.0.0.1:1936/app-secret-stream-key;
        }
    }
}

http {
    server {
        listen 80;
        server_name rtmp.example.com;

        location ^~ /.well-known/acme-challenge/ {
            root /var/www/letsencrypt;
        }
        location / {
            return 404;
        }
    }
}

Nginx RTMPS + secret publishing key + IP address based access control

I decided to post this as another answer, as my first answer is still a good explanatory answer to keep, and I also wanted to give credits to Danila Vershinin for pointing out using Nginx's stream{}. However, while both these answers increases security by encrypting the contents including the key, they also remove the ability of access control using allow/deny [play|publish] address|subnet|all of the rtmp{} module.

The stream{} i.e. proxied TCP has own access control, but (unlike rtmp{}) it can't distinguish publishing from playing: with a single stream{} proxy everyone can both publish & play – or is denied from doing neither one. Therefore, an access control using both keys and IP restrictions requires a structure with separate proxies for both publishing and streaming: a separete TCP port for proxying the publishing with the key. The following diagram demonstrates this design:

enter image description here

Here, I use the standard port 1935/tcp for the RTMPS-play and an additional 1936/tcp for the RTMPS-publish. For the internal unencrypted RTMP connections I use similar ports 19351 and 19361. The red color represents unencrypted connections & untrusted networks, whereas the green color represents encrypted connections & trusted networks.

The proxied TCP now has two (RTMPS) configurations, but both can still use the same certificate:

stream {
    upstream publish {
        server 127.0.0.1:19361;
    }
    server {
        listen 1936 ssl;        # additional port for publishing
        proxy_pass publish;
        ssl_certificate /etc/letsencrypt/live/rtmp.example.com/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/rtmp.example.com/privkey.pem;

        allow 192.0.2.1;        # allow publish from this IP
        allow 192.0.2.0/24;     # -- also supports CIDR notation!
        deny all;               # deny publish from the rest
    }

    upstream live {
        server 127.0.0.1:19351;
    }
    server {
        listen 1935 ssl;        # standard RTMP(S) port
        proxy_pass live;
        ssl_certificate /etc/letsencrypt/live/rtmp.example.com/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/rtmp.example.com/privkey.pem;

        allow all;              # this is public (this is also the default)
    }
}

Likewise, we now need two separate (local loopback) RTMP servers for each application:

rtmp {
    server {
        listen 127.0.0.1:19361;
        chunk_size 4096;

        application secret-key {
            live on;
            record off;
            allow publish 127.0.0.1;  # publishing through rtmps://rtmp.example.com:1936
            allow play 127.0.0.1;     # for the pull from rtmp://localhost:19351/live
        }
    }

    server {
        listen 127.0.0.1:19351;
        chunk_size 4096;

        application live {
            live on;
            record off;
            deny publish all;         # no need to publish on /live -- IMPORTANT!!!
            allow play 127.0.0.1;     # playing through rtmps://rtmp.example.com:1935/live

            pull rtmp://127.0.0.1:19361/secret-key;
        }
    }
}

The actual IP based access control is done on the stream{} section, so only the deny publish all; is mandatory for preventing direct publishing using the /live application. I've added the allow directives to the rtmp{} section just to clarify (and comment on) the default behaviour of the RTMP access control.