{"id":1007,"date":"2026-05-06T11:41:29","date_gmt":"2026-05-06T04:41:29","guid":{"rendered":"https:\/\/liveapi.com\/blog\/nginx-rtmp\/"},"modified":"2026-05-06T11:41:59","modified_gmt":"2026-05-06T04:41:59","slug":"nginx-rtmp","status":"publish","type":"post","link":"https:\/\/liveapi.com\/blog\/nginx-rtmp\/","title":{"rendered":"Nginx RTMP: How to Set Up a Live Streaming Server (Complete Guide)"},"content":{"rendered":"<span class=\"rt-reading-time\" style=\"display: block;\"><span class=\"rt-label rt-prefix\">Reading Time: <\/span> <span class=\"rt-time\">10<\/span> <span class=\"rt-label rt-postfix\">minutes<\/span><\/span><p>If you&#8217;ve ever wanted to run your own live streaming server without paying for a cloud platform, the nginx rtmp module is probably the first project you read about. It turns the same web server that powers a third of the internet into a working RTMP ingest endpoint, an HLS packager, and a multi-platform relay \u2014 for the cost of a small VPS.<\/p>\n<p>This guide walks through what the nginx rtmp module actually does, how the data flow works, every directive you need for a basic setup, an example that produces both RTMP playback and an HLS playlist, the trade-offs you&#8217;ll hit at scale, and when it makes more sense to send your stream to a managed API instead.<\/p>\n<h2>What Is Nginx RTMP?<\/h2>\n<p>Nginx rtmp (also written as nginx-rtmp-module) is an open-source nginx module that adds support for the <a href=\"https:\/\/liveapi.com\/blog\/what-is-rtmp\/\" target=\"_blank\">RTMP protocol<\/a>, HLS, and MPEG-DASH to the nginx web server. Once compiled in or loaded as a dynamic module, nginx accepts RTMP publishes on TCP port 1935, plays the streams back to RTMP viewers, and can package the same input as HLS or DASH segments served over HTTP.<\/p>\n<p>The original project \u2014 <a href=\"https:\/\/github.com\/arut\/nginx-rtmp-module\" target=\"_blank\" rel=\"nofollow\">arut\/nginx-rtmp-module<\/a> \u2014 was created by Roman Arutyunyan and is licensed BSD-2-Clause. It has 14,000+ stars on GitHub, runs on Linux, FreeBSD, macOS, and Windows, and is the most widely deployed open-source <a href=\"https:\/\/liveapi.com\/blog\/rtmp-server\/\" target=\"_blank\">RTMP server<\/a> in the developer community. Active development on the original repo slowed years ago, but several community forks (notably nginx-rtmp-module by sergey-dryabzhinsky) keep it patched against modern nginx releases.<\/p>\n<table>\n<thead>\n<tr>\n<th>Attribute<\/th>\n<th>Value<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Project<\/td>\n<td>nginx-rtmp-module<\/td>\n<\/tr>\n<tr>\n<td>License<\/td>\n<td>BSD-2-Clause<\/td>\n<\/tr>\n<tr>\n<td>Default ingest port<\/td>\n<td>1935 (TCP)<\/td>\n<\/tr>\n<tr>\n<td>Ingest protocol<\/td>\n<td>RTMP, RTMPS (with stunnel)<\/td>\n<\/tr>\n<tr>\n<td>Output protocols<\/td>\n<td>RTMP, HLS, MPEG-DASH, FLV<\/td>\n<\/tr>\n<tr>\n<td>Codecs<\/td>\n<td>H.264 video, AAC audio (passthrough; transcoding via FFmpeg <code>exec<\/code>)<\/td>\n<\/tr>\n<tr>\n<td>Recording<\/td>\n<td>FLV files via <code>record<\/code> directive<\/td>\n<\/tr>\n<tr>\n<td>Authentication<\/td>\n<td>HTTP callback (<code>on_publish<\/code>, <code>on_play<\/code>)<\/td>\n<\/tr>\n<tr>\n<td>Platforms<\/td>\n<td>Linux, FreeBSD, macOS, Windows (limited)<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h2>How Does Nginx RTMP Work?<\/h2>\n<p>Nginx rtmp adds an <code>rtmp { }<\/code> block at the top level of <code>nginx.conf<\/code>, parallel to the standard <code>http { }<\/code> block. When nginx starts with the module loaded, it opens a TCP listener on port 1935 and waits for RTMP clients.<\/p>\n<p>Here&#8217;s the data flow for a typical OBS-to-browser stream:<\/p>\n<ol>\n<li><strong>Publisher connects.<\/strong> OBS, FFmpeg, or another <a href=\"https:\/\/liveapi.com\/blog\/rtmp-encoder\/\" target=\"_blank\">RTMP encoder<\/a> opens a TCP connection to <code>rtmp:\/\/your-server\/live\/streamkey<\/code>. Nginx accepts it and matches the URL against an <code>application<\/code> block.<\/li>\n<li><strong>Stream is received.<\/strong> Nginx parses the RTMP handshake, then begins accepting H.264 video and AAC audio chunks. The <code>chunk_size 4096;<\/code> directive controls how big each RTMP message can be.<\/li>\n<li><strong>Stream is fanned out.<\/strong> With <code>live on;<\/code>, nginx broadcasts the incoming feed to every RTMP viewer that has subscribed to the same path. This is the classic publisher-to-many-subscribers pattern.<\/li>\n<li><strong>Optional packaging.<\/strong> If <code>hls on;<\/code> is set, nginx writes <code>.ts<\/code> segments and an <code>.m3u8<\/code> playlist to disk every few seconds. An HTTP server block then serves those files to browsers and mobile players. This is the same packaging step you read about in <a href=\"https:\/\/liveapi.com\/blog\/rtmp-to-hls\/\" target=\"_blank\">RTMP to HLS conversion<\/a>.<\/li>\n<li><strong>Optional recording or relay.<\/strong> With <code>record all;<\/code>, nginx writes a <code>.flv<\/code> file for each session. With <code>push rtmp:\/\/otherserver\/live\/key;<\/code>, it forwards the stream to another endpoint \u2014 useful for multi-destination broadcasts.<\/li>\n<\/ol>\n<p>The module never re-encodes by default. It moves bytes from publisher to subscriber and writes segments. If you need transcoding (different bitrates for <a href=\"https:\/\/liveapi.com\/blog\/adaptive-bitrate-streaming\/\" target=\"_blank\">adaptive bitrate streaming<\/a>, or codec changes), you wire up an <code>exec<\/code> directive that calls <code>ffmpeg<\/code> per stream.<\/p>\n<h2>Key Features of the Nginx RTMP Module<\/h2>\n<p>The feature set is small but covers the core workflows for self-hosted live streaming.<\/p>\n<h3>RTMP Ingest and Playback<\/h3>\n<p>The module accepts RTMP publishes and serves RTMP playback on the same port. This is the use case 90% of people install it for: an open ingest URL that OBS, vMix, FFmpeg, or any <a href=\"https:\/\/liveapi.com\/blog\/live-streaming-encoder\/\" target=\"_blank\">live streaming encoder<\/a> can push to.<\/p>\n<h3>HLS and DASH Packaging<\/h3>\n<p>Setting <code>hls on;<\/code> and <code>dash on;<\/code> turns the same RTMP input into an HTTP-delivered stream that browsers and mobile devices can play. This is what makes nginx rtmp practical for web playback \u2014 without it, you&#8217;d be limited to Flash players, which no modern browser supports.<\/p>\n<h3>Multi-Worker Streaming<\/h3>\n<p>The <code>rtmp_auto_push on;<\/code> directive lets nginx run multiple worker processes and automatically relay streams between them, so you can use all your CPU cores instead of being capped at one.<\/p>\n<h3>Stream Relay (Push and Pull)<\/h3>\n<p><code>push<\/code> sends an incoming stream to one or more downstream RTMP servers. <code>pull<\/code> does the reverse \u2014 nginx fetches a remote stream when a viewer connects. This is how you build a small CDN tier or restream to YouTube and Twitch from a single ingest point.<\/p>\n<h3>Recording<\/h3>\n<p>The <code>record<\/code> directive saves incoming streams to FLV files. You can capture every session, only on demand via control API, or by file size. This is the typical building block for <a href=\"https:\/\/liveapi.com\/blog\/live-rtmp-stream\/\" target=\"_blank\">live RTMP stream<\/a> archive workflows.<\/p>\n<h3>HTTP Callbacks<\/h3>\n<p><code>on_publish<\/code>, <code>on_play<\/code>, <code>on_record_done<\/code>, and similar hooks fire HTTP requests to your application when streams start, stop, or finish recording. You use them for authentication, billing, and to trigger downstream processing.<\/p>\n<h3>Statistics<\/h3>\n<p>The module exposes an XML stats endpoint that you can serve through any HTTP location and style with the included XSL stylesheet. It shows active publishers, subscribers, bandwidth, and per-stream metadata.<\/p>\n<h2>Nginx RTMP vs. Other Streaming Servers<\/h2>\n<p>Nginx rtmp is one of several open-source RTMP server projects. Here&#8217;s how it compares to the most common alternatives:<\/p>\n<table>\n<thead>\n<tr>\n<th>Server<\/th>\n<th>Protocols<\/th>\n<th>License<\/th>\n<th>Best For<\/th>\n<th>Trade-Off<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><strong>nginx rtmp<\/strong><\/td>\n<td>RTMP, HLS, DASH<\/td>\n<td>BSD-2<\/td>\n<td>Simple self-hosted ingest + HLS<\/td>\n<td>No WebRTC, slow upstream maintenance<\/td>\n<\/tr>\n<tr>\n<td><strong>SRS (Simple Realtime Server)<\/strong><\/td>\n<td>RTMP, WebRTC, HLS, HTTP-FLV, SRT, DASH, GB28181<\/td>\n<td>MIT<\/td>\n<td>Higher concurrency, modern protocols<\/td>\n<td>Steeper learning curve<\/td>\n<\/tr>\n<tr>\n<td><strong>Ant Media Server<\/strong><\/td>\n<td>WebRTC, RTMP, SRT, HLS, CMAF<\/td>\n<td>Apache 2 (Community) \/ Commercial<\/td>\n<td>Sub-second WebRTC + transcoding<\/td>\n<td>Heavier, JVM-based<\/td>\n<\/tr>\n<tr>\n<td><strong>MistServer<\/strong><\/td>\n<td>RTMP, WebRTC, HLS, DASH, MP4<\/td>\n<td>Custom<\/td>\n<td>Many output formats<\/td>\n<td>Less community<\/td>\n<\/tr>\n<tr>\n<td><strong>Wowza Streaming Engine<\/strong><\/td>\n<td>RTMP, WebRTC, SRT, HLS, DASH<\/td>\n<td>Commercial<\/td>\n<td>Enterprise features, support<\/td>\n<td>Paid license<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>If you only need RTMP ingest and HLS output and you&#8217;re comfortable in nginx config, the nginx rtmp module is the smallest, most predictable option. If you need WebRTC for sub-second latency, SRS or Ant Media is the better starting point. Compare more options in our list of the <a href=\"https:\/\/liveapi.com\/blog\/best-video-streaming-servers\/\" target=\"_blank\">best video streaming servers<\/a>.<\/p>\n<h2>How to Install the Nginx RTMP Module<\/h2>\n<p>There are two paths: install a pre-packaged version, or build nginx from source with the module compiled in. The first is faster; the second gives you the latest module version and lets you patch it.<\/p>\n<h3>Option 1: Install via Apt (Ubuntu\/Debian)<\/h3>\n<p>The <code>libnginx-mod-rtmp<\/code> package ships in the Ubuntu repos and works for most use cases.<\/p>\n<pre><code class=\"language-bash\">sudo apt update\nsudo apt install libnginx-mod-rtmp<\/code><\/pre>\n<p>This installs nginx (if it&#8217;s not already present) and the RTMP dynamic module. The module is auto-loaded via <code>\/etc\/nginx\/modules-enabled\/50-mod-rtmp.conf<\/code>.<\/p>\n<h3>Option 2: Build From Source<\/h3>\n<p>Use this when you need the latest module commit, want HLS encryption, or are running a non-Debian distribution.<\/p>\n<pre><code class=\"language-bash\">sudo apt install -y build-essential libpcre3-dev libssl-dev zlib1g-dev git\ncd \/tmp\nwget https:\/\/nginx.org\/download\/nginx-1.26.2.tar.gz\ntar -xzf nginx-1.26.2.tar.gz\ngit clone https:\/\/github.com\/arut\/nginx-rtmp-module.git\ncd nginx-1.26.2\n.\/configure --with-http_ssl_module --add-module=\/tmp\/nginx-rtmp-module\nmake\nsudo make install<\/code><\/pre>\n<p>Nginx will install to <code>\/usr\/local\/nginx<\/code>. Symlink the binary into your path, then create a systemd unit so it starts on boot.<\/p>\n<h3>Open the Firewall<\/h3>\n<p>RTMP listens on TCP 1935. If you&#8217;re running UFW:<\/p>\n<pre><code class=\"language-bash\">sudo ufw allow 1935\/tcp\nsudo ufw allow 80\/tcp\nsudo ufw allow 8080\/tcp<\/code><\/pre>\n<p>Port 80 or 8080 is for HTTP delivery of HLS segments. Port 1935 is for the RTMP ingest itself.<\/p>\n<h2>Basic Nginx RTMP Configuration<\/h2>\n<p>The minimum working config for a private RTMP server is about 15 lines. Add this to <code>\/etc\/nginx\/nginx.conf<\/code> (or <code>\/usr\/local\/nginx\/conf\/nginx.conf<\/code> if you built from source):<\/p>\n<pre><code class=\"language-nginx\">load_module modules\/ngx_rtmp_module.so;\n\nevents {\n    worker_connections 1024;\n}\n\nrtmp {\n    server {\n        listen 1935;\n        chunk_size 4096;\n\n        application live {\n            live on;\n            record off;\n            allow publish 127.0.0.1;\n            allow publish 192.168.1.0\/24;\n            deny publish all;\n            allow play all;\n        }\n    }\n}\n\nhttp {\n    server {\n        listen 80;\n        location \/stat {\n            rtmp_stat all;\n            rtmp_stat_stylesheet stat.xsl;\n        }\n    }\n}<\/code><\/pre>\n<p>After saving, run <code>sudo nginx -t<\/code> to validate syntax, then <code>sudo systemctl reload nginx<\/code>. You can now publish to <code>rtmp:\/\/<server-ip>\/live\/<streamkey><\/code> from OBS or FFmpeg, and play back from VLC at the same URL.<\/p>\n<p>A few directives worth understanding:<\/p>\n<ul>\n<li><strong><code>live on;<\/code><\/strong> \u2014 Enables broadcast mode. Without it, nginx accepts publishes but doesn&#8217;t relay to subscribers.<\/li>\n<li><strong><code>record off;<\/code><\/strong> \u2014 Disables disk recording. Set to <code>record all;<\/code> to capture every session.<\/li>\n<li><strong><code>allow publish<\/code> \/ <code>deny publish<\/code><\/strong> \u2014 IP-based access control for publishers. Always restrict to your own IPs in production.<\/li>\n<li><strong><code>chunk_size 4096;<\/code><\/strong> \u2014 Larger chunks reduce CPU overhead at the cost of slightly higher latency. 4096 is a safe default.<\/li>\n<\/ul>\n<h2>Adding HLS Output for Browser Playback<\/h2>\n<p>RTMP playback works for VLC and other RTMP-aware players, but not for browsers. To stream to a <code><video><\/code> tag, add HLS packaging:<\/p>\n<pre><code class=\"language-nginx\">rtmp {\n    server {\n        listen 1935;\n        chunk_size 4096;\n\n        application live {\n            live on;\n            record off;\n\n            hls on;\n            hls_path \/var\/www\/hls;\n            hls_fragment 3;\n            hls_playlist_length 60;\n        }\n    }\n}\n\nhttp {\n    server {\n        listen 8080;\n\n        location \/hls {\n            types {\n                application\/vnd.apple.mpegurl m3u8;\n                video\/mp2t ts;\n            }\n            root \/var\/www;\n            add_header Cache-Control no-cache;\n            add_header Access-Control-Allow-Origin *;\n        }\n    }\n}<\/code><\/pre>\n<p>Create the HLS directory and give nginx write access:<\/p>\n<pre><code class=\"language-bash\">sudo mkdir -p \/var\/www\/hls\nsudo chown www-data:www-data \/var\/www\/hls<\/code><\/pre>\n<p>When a publisher pushes to <code>rtmp:\/\/server\/live\/test<\/code>, nginx will write <code>\/var\/www\/hls\/test.m3u8<\/code> plus a rolling set of <code>.ts<\/code> segments. A browser using hls.js can play it from <code>http:\/\/server:8080\/hls\/test.m3u8<\/code>. This is the same packaging logic explained in our deeper <a href=\"https:\/\/liveapi.com\/blog\/what-is-hls-streaming\/\" target=\"_blank\">HLS streaming<\/a> write-up.<\/p>\n<p>The <code>hls_fragment<\/code> setting controls segment length, which directly affects latency. Three seconds is the sweet spot for stability; one second pushes toward <a href=\"https:\/\/liveapi.com\/blog\/what-is-low-latency-streaming\/\" target=\"_blank\">low latency streaming<\/a> but increases segment overhead.<\/p>\n<h2>Advanced Configuration: Recording, Relay, and Auth<\/h2>\n<p>Once the basic stream is working, three patterns come up almost immediately.<\/p>\n<h3>Recording Every Stream<\/h3>\n<pre><code class=\"language-nginx\">application live {\n    live on;\n    record all;\n    record_path \/var\/recordings;\n    record_unique on;\n    record_suffix -%Y-%m-%d-%H_%M_%S.flv;\n}<\/code><\/pre>\n<p><code>record_unique on;<\/code> adds a timestamp so concurrent sessions don&#8217;t overwrite each other. To convert the resulting FLV into MP4 after recording, use the <code>on_record_done<\/code> callback to trigger an FFmpeg job.<\/p>\n<h3>Restreaming to YouTube and Twitch<\/h3>\n<pre><code class=\"language-nginx\">application live {\n    live on;\n    record off;\n    push rtmp:\/\/a.rtmp.youtube.com\/live2\/YOUR-YOUTUBE-KEY;\n    push rtmp:\/\/live.twitch.tv\/app\/YOUR-TWITCH-KEY;\n}<\/code><\/pre>\n<p>Each push opens a separate outbound RTMP connection. This is fine for two or three destinations on a small VPS, but it eats upstream bandwidth fast \u2014 every destination is a full copy of your bitrate.<\/p>\n<h3>HTTP Callback Authentication<\/h3>\n<pre><code class=\"language-nginx\">application live {\n    live on;\n    on_publish http:\/\/127.0.0.1:3000\/auth;\n    on_publish_done http:\/\/127.0.0.1:3000\/done;\n}<\/code><\/pre>\n<p>Nginx posts the stream name, IP, and metadata to your endpoint. Return HTTP 2xx to allow the publish, anything else to reject. This is how most production deployments enforce stream keys \u2014 the keys live in your application database, not in <code>nginx.conf<\/code>.<\/p>\n<h3>Multi-Bitrate via FFmpeg <code>exec<\/code><\/h3>\n<pre><code class=\"language-nginx\">application live {\n    live on;\n    exec ffmpeg -i rtmp:\/\/localhost\/live\/$name\n      -c:v libx264 -b:v 2500k -s 1280x720 -f flv rtmp:\/\/localhost\/hls\/$name_720p\n      -c:v libx264 -b:v 1000k -s 854x480 -f flv rtmp:\/\/localhost\/hls\/$name_480p\n      -c:v libx264 -b:v 500k -s 640x360 -f flv rtmp:\/\/localhost\/hls\/$name_360p;\n}<\/code><\/pre>\n<p>This forks an FFmpeg process per publish that creates 720p, 480p, and 360p renditions. It&#8217;s CPU-heavy \u2014 a four-core box will choke on more than two or three concurrent streams.<\/p>\n<h2>Streaming to Your Nginx RTMP Server<\/h2>\n<p>Once nginx is running, any RTMP-capable client can publish. The two most common are OBS and FFmpeg.<\/p>\n<p><strong>OBS Studio:<\/strong> Settings \u2192 Stream \u2192 Service &#8220;Custom&#8221; \u2192 Server <code>rtmp:\/\/your-server\/live<\/code> \u2192 Stream Key <code>anything<\/code>. Click Start Streaming.<\/p>\n<p><strong>FFmpeg from a file:<\/strong><\/p>\n<pre><code class=\"language-bash\">ffmpeg -re -i video.mp4 -c:v copy -c:a aac -ar 44100 -ac 1 \\\n  -f flv rtmp:\/\/your-server\/live\/test<\/code><\/pre>\n<p>The <code>-re<\/code> flag tells FFmpeg to read at the file&#8217;s native frame rate, which is what makes it behave like a live source. The <code>-c:v copy<\/code> flag avoids re-encoding. Test playback from VLC: Media \u2192 Open Network Stream \u2192 <code>rtmp:\/\/your-server\/live\/test<\/code>.<\/p>\n<p>For HLS playback, point hls.js or any browser HLS player at <code>http:\/\/your-server:8080\/hls\/test.m3u8<\/code>. There&#8217;s a 6\u201320 second latency depending on <code>hls_fragment<\/code> and <code>hls_playlist_length<\/code>, which is normal for HLS.<\/p>\n<h2>Limitations of Nginx RTMP<\/h2>\n<p>The module is reliable for what it does, but the gaps matter once you move past hobby use.<\/p>\n<h3>Original Repository Is Slow-Moving<\/h3>\n<p>The upstream <code>arut\/nginx-rtmp-module<\/code> repo has 1,100+ open issues and infrequent releases. Most production users compile against a community fork or accept that bugs may persist for years. Compare that to a managed API where someone else owns the patches.<\/p>\n<h3>No WebRTC, No Sub-Second Latency<\/h3>\n<p>HLS bottoms out around 5\u201310 seconds end-to-end, even with one-second fragments. If you need real-time interaction, you&#8217;re looking at WebRTC instead \u2014 see our <a href=\"https:\/\/liveapi.com\/blog\/webrtc-vs-rtmp\/\" target=\"_blank\">WebRTC vs RTMP<\/a> breakdown for the trade-off.<\/p>\n<h3>Single-Server Scalability<\/h3>\n<p>A 4-core VPS handles 30\u201350 concurrent RTMP relays without transcoding. Add per-stream FFmpeg transcoding for ABR and that drops to 3\u20135 streams per box. Scaling beyond that means an origin-edge topology you build yourself, plus a <a href=\"https:\/\/liveapi.com\/blog\/cdn-for-live-streaming\/\" target=\"_blank\">CDN for live streaming<\/a> in front for HLS distribution.<\/p>\n<h3>No Built-In ABR Manifest<\/h3>\n<p><code>hls on;<\/code> produces a single-rendition <code>.m3u8<\/code>. Real adaptive bitrate streaming requires you to transcode multiple renditions (with FFmpeg <code>exec<\/code>) and manually write a master playlist that references each variant. There&#8217;s no <code>nginx.conf<\/code> directive that does this for you.<\/p>\n<h3>Security Surface<\/h3>\n<p>Without <code>allow publish<\/code> rules and HTTP callback auth, your RTMP endpoint is open to the world. There&#8217;s no built-in TLS for RTMP \u2014 you need a stunnel or HAProxy front-end for RTMPS. Compare with managed platforms that handle <a href=\"https:\/\/liveapi.com\/blog\/srt-protocol\/\" target=\"_blank\">SRT protocol<\/a> and RTMPS by default.<\/p>\n<h3>Codec Lock-In<\/h3>\n<p>H.264 and AAC are the only first-class codecs. H.265, AV1, and Opus all require careful FFmpeg <code>exec<\/code> workflows and may not play through the HLS packager without manual segment generation. Modern codecs covered in our <a href=\"https:\/\/liveapi.com\/blog\/hevc-vs-h264\/\" target=\"_blank\">HEVC vs H.264<\/a> post are essentially out of scope for stock nginx rtmp.<\/p>\n<h2>When to Use Nginx RTMP \u2014 and When to Use a Managed API<\/h2>\n<p>Self-hosting nginx rtmp pays off when bandwidth is cheap and you control both ends of the pipe. It breaks down when audiences span continents, when stream counts go over a few dozen, or when &#8220;the stream went down at 2am&#8221; becomes a business problem instead of a personal one.<\/p>\n<p>Three rough heuristics:<\/p>\n<ul>\n<li><strong>Choose nginx rtmp<\/strong> when you have under 20 concurrent streams, viewers are in one region, you&#8217;re comfortable in nginx config, and downtime is acceptable.<\/li>\n<li><strong>Choose a hybrid setup<\/strong> (nginx rtmp ingest behind a CDN) when you need broader reach but want to keep encoder and authentication logic local.<\/li>\n<li><strong>Choose a managed RTMP API<\/strong> when you ship a product, your viewers are global, you need <a href=\"https:\/\/liveapi.com\/blog\/adaptive-bit-rate\/\" target=\"_blank\">adaptive bitrate streaming<\/a> automatically, and engineering time is more expensive than infrastructure.<\/li>\n<\/ul>\n<p>LiveAPI fills the third bucket. The <a href=\"https:\/\/liveapi.com\/live-streaming-api\/\" target=\"_blank\">live streaming API<\/a> gives you the same <code>rtmp:\/\/<\/code> ingest endpoint you&#8217;d point OBS at \u2014 except it&#8217;s backed by automatic transcoding, ABR ladder generation, multi-CDN HLS delivery (Akamai, Cloudflare, Fastly), an embeddable HTML5 player, <a href=\"https:\/\/liveapi.com\/blog\/srt-vs-rtmp\/\" target=\"_blank\">SRT vs RTMP<\/a> ingest, webhooks, and live-to-VOD recording. There&#8217;s no <code>nginx.conf<\/code>, no FFmpeg <code>exec<\/code> chains, and no firewall rules to maintain. Pricing is per-minute, so you only pay for streams that actually run.<\/p>\n<p>For teams choosing between rolling their own and using an API, our <a href=\"https:\/\/liveapi.com\/blog\/best-live-streaming-apis\/\" target=\"_blank\">best live streaming APIs<\/a> post compares the major options side by side.<\/p>\n<h2>Nginx RTMP FAQ<\/h2>\n<h3>Is nginx rtmp still maintained?<\/h3>\n<p>The original <code>arut\/nginx-rtmp-module<\/code> repo on GitHub receives infrequent updates, but several active forks (notably the one by sergey-dryabzhinsky) keep it building against current nginx releases. The Ubuntu <code>libnginx-mod-rtmp<\/code> package usually tracks one of these forks.<\/p>\n<h3>Can nginx rtmp do RTMPS?<\/h3>\n<p>Not directly \u2014 the module only speaks plain RTMP on TCP 1935. To accept RTMPS publishes, run stunnel or HAProxy in front of nginx, terminating TLS on port 443 and forwarding plain RTMP to localhost:1935. Some FFmpeg builds with <code>--enable-openssl<\/code> can publish RTMPS; check <code>ffmpeg -protocols<\/code>.<\/p>\n<h3>How many concurrent streams can nginx rtmp handle?<\/h3>\n<p>A 4-core, 8 GB VPS handles 30\u201350 concurrent RTMP relays (no transcoding) comfortably. With per-stream FFmpeg transcoding for ABR, expect 3\u20135 streams per box. Bandwidth is usually the limit before CPU on cloud VPS instances.<\/p>\n<h3>Does nginx rtmp work on Windows?<\/h3>\n<p>Yes, but with caveats. The module compiles on Windows, but <code>exec<\/code> directives, static pulls, and <code>auto_push<\/code> are not supported. For Windows development, most people run nginx rtmp inside Docker or WSL2 instead of native Windows.<\/p>\n<h3>How do I generate stream keys for nginx rtmp?<\/h3>\n<p>Stream keys are just the path component of the RTMP URL \u2014 <code>rtmp:\/\/server\/live\/<key><\/code> is the key. To validate them, point <code>on_publish<\/code> at your application server, look up the key in your database, and return HTTP 200 to accept or 403 to reject.<\/p>\n<h3>What&#8217;s the difference between RTMP and RTMPS in nginx?<\/h3>\n<p>RTMP is plain TCP on port 1935. RTMPS adds TLS, usually on port 443. Nginx rtmp only handles RTMP natively; you wrap it in stunnel for RTMPS. Both speak the same FLV-tagged H.264\/AAC payload \u2014 only the transport differs.<\/p>\n<h3>Can nginx rtmp output to MPEG-DASH?<\/h3>\n<p>Yes \u2014 set <code>dash on;<\/code>, <code>dash_path \/var\/www\/dash;<\/code>, and serve the DASH directory over HTTP, similar to the HLS setup. DASH is useful for browsers without HLS support, though most modern players handle both.<\/p>\n<h3>Is nginx rtmp free?<\/h3>\n<p>The module is BSD-2-Clause licensed and free to use commercially. Costs come from the VPS or bare-metal server you run it on, plus bandwidth \u2014 both of which scale linearly with stream count and viewer count.<\/p>\n<h2>Closing Thoughts<\/h2>\n<p>The nginx rtmp module is the cheapest way to get a working RTMP ingest endpoint with HLS output, and for small projects it&#8217;s still the right choice. The catch is that everything past &#8220;one stream, a few viewers, one region&#8221; turns into work you have to do yourself: ABR transcoding, multi-region delivery, RTMPS, monitoring, recording rotation, and bug patches the upstream repo hasn&#8217;t merged.<\/p>\n<p>If you&#8217;ve already built the second version of that work and you&#8217;re tired of maintaining it, <a href=\"https:\/\/liveapi.com\/\" target=\"_blank\">Get started with LiveAPI<\/a> \u2014 it gives you the same RTMP ingest URL with managed transcoding, multi-CDN delivery, and a player baked in.<\/p>\n","protected":false},"excerpt":{"rendered":"<p><span class=\"rt-reading-time\" style=\"display: block;\"><span class=\"rt-label rt-prefix\">Reading Time: <\/span> <span class=\"rt-time\">10<\/span> <span class=\"rt-label rt-postfix\">minutes<\/span><\/span> If you&#8217;ve ever wanted to run your own live streaming server without paying for a cloud platform, the nginx rtmp module is probably the first project you read about. It turns the same web server that powers a third of the internet into a working RTMP ingest endpoint, an HLS packager, and a multi-platform relay [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":1008,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_yoast_wpseo_title":"Nginx RTMP: Setup, Config, and HLS Streaming Guide %%sep%% %%sitename%%","_yoast_wpseo_metadesc":"Learn what nginx rtmp is, how it works, full installation and config steps, HLS output, alternatives, and when to use a managed RTMP API instead.","inline_featured_image":false,"footnotes":""},"categories":[5],"tags":[],"class_list":["post-1007","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-rtmp"],"jetpack_featured_media_url":"https:\/\/liveapi.com\/blog\/wp-content\/uploads\/2026\/05\/nginx-rtmp.jpg","yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v15.6.2 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<meta name=\"description\" content=\"Learn what nginx rtmp is, how it works, full installation and config steps, HLS output, alternatives, and when to use a managed RTMP API instead.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/liveapi.com\/blog\/nginx-rtmp\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Nginx RTMP: Setup, Config, and HLS Streaming Guide - LiveAPI Blog\" \/>\n<meta property=\"og:description\" content=\"Learn what nginx rtmp is, how it works, full installation and config steps, HLS output, alternatives, and when to use a managed RTMP API instead.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/liveapi.com\/blog\/nginx-rtmp\/\" \/>\n<meta property=\"og:site_name\" content=\"LiveAPI Blog\" \/>\n<meta property=\"article:published_time\" content=\"2026-05-06T04:41:29+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-05-06T04:41:59+00:00\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Est. reading time\">\n\t<meta name=\"twitter:data1\" content=\"15 minutes\">\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebSite\",\"@id\":\"https:\/\/liveapi.com\/blog\/#website\",\"url\":\"https:\/\/liveapi.com\/blog\/\",\"name\":\"LiveAPI Blog\",\"description\":\"Live Video Streaming API Blog\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":\"https:\/\/liveapi.com\/blog\/?s={search_term_string}\",\"query-input\":\"required name=search_term_string\"}],\"inLanguage\":\"en-US\"},{\"@type\":\"ImageObject\",\"@id\":\"https:\/\/liveapi.com\/blog\/nginx-rtmp\/#primaryimage\",\"inLanguage\":\"en-US\",\"url\":\"https:\/\/liveapi.com\/blog\/wp-content\/uploads\/2026\/05\/nginx-rtmp.jpg\",\"width\":940,\"height\":628,\"caption\":\"Photo by panumas nikhomkhai on Pexels\"},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/liveapi.com\/blog\/nginx-rtmp\/#webpage\",\"url\":\"https:\/\/liveapi.com\/blog\/nginx-rtmp\/\",\"name\":\"Nginx RTMP: Setup, Config, and HLS Streaming Guide - LiveAPI Blog\",\"isPartOf\":{\"@id\":\"https:\/\/liveapi.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/liveapi.com\/blog\/nginx-rtmp\/#primaryimage\"},\"datePublished\":\"2026-05-06T04:41:29+00:00\",\"dateModified\":\"2026-05-06T04:41:59+00:00\",\"author\":{\"@id\":\"https:\/\/liveapi.com\/blog\/#\/schema\/person\/98f2ee8b3a0bd93351c0d9e8ce490e4a\"},\"description\":\"Learn what nginx rtmp is, how it works, full installation and config steps, HLS output, alternatives, and when to use a managed RTMP API instead.\",\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/liveapi.com\/blog\/nginx-rtmp\/\"]}]},{\"@type\":\"Person\",\"@id\":\"https:\/\/liveapi.com\/blog\/#\/schema\/person\/98f2ee8b3a0bd93351c0d9e8ce490e4a\",\"name\":\"govz\",\"image\":{\"@type\":\"ImageObject\",\"@id\":\"https:\/\/liveapi.com\/blog\/#personlogo\",\"inLanguage\":\"en-US\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/ab5cbe0543c0a44dc944c720159323bd001fc39a8ba5b1f137cd22e7578e84c9?s=96&d=mm&r=g\",\"caption\":\"govz\"},\"sameAs\":[\"https:\/\/liveapi.com\/blog\"]}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","_links":{"self":[{"href":"https:\/\/liveapi.com\/blog\/wp-json\/wp\/v2\/posts\/1007","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/liveapi.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/liveapi.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/liveapi.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/liveapi.com\/blog\/wp-json\/wp\/v2\/comments?post=1007"}],"version-history":[{"count":1,"href":"https:\/\/liveapi.com\/blog\/wp-json\/wp\/v2\/posts\/1007\/revisions"}],"predecessor-version":[{"id":1009,"href":"https:\/\/liveapi.com\/blog\/wp-json\/wp\/v2\/posts\/1007\/revisions\/1009"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/liveapi.com\/blog\/wp-json\/wp\/v2\/media\/1008"}],"wp:attachment":[{"href":"https:\/\/liveapi.com\/blog\/wp-json\/wp\/v2\/media?parent=1007"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/liveapi.com\/blog\/wp-json\/wp\/v2\/categories?post=1007"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/liveapi.com\/blog\/wp-json\/wp\/v2\/tags?post=1007"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}