CentOS 8: Deploying a Production Streaming Server with Nginx and RTMP
Monolithic streaming platforms come with steep costs and buried restrictions. When requirements demand control, transparency, or low-level tuning, running your own infrastructure is the answer. Here’s a stepwise deployment of a streaming server stack on CentOS 8, using Nginx compiled with the RTMP module—a practical approach favored when vendor lock-in and pricing headaches become blockers.
Scenario
Suppose your organization needs to livestream internal events for hundreds of remote employees. Commercial solutions quote $500+/mo for white-label, predictable latency, and simple HLS delivery—yet you have a robust CentOS 8 VM with unmetered bandwidth. Building your own stack is not just a cost optimization, it’s an architectural win.
Pre-Deployment
- System: CentOS 8 (any recent minor version, e.g. 8.8)
- Privileges: root or sudoer
- Network: Open TCP/1935 (RTMP), TCP/8080 (HTTP), or alternative ports as per your firewall policy
Verify initial state:
$ uname -r
4.18.0-477.21.1.el8_8.x86_64
$ sudo firewall-cmd --add-port=1935/tcp --add-port=8080/tcp --permanent && \
sudo firewall-cmd --reload
System Patch and Toolchain Setup
This deployment hinges on building Nginx with a third-party RTMP module. Stock repo packages don’t offer this by default.
sudo dnf update -y
sudo dnf install -y epel-release
sudo dnf groupinstall "Development Tools" -y
sudo dnf install -y gcc make pcre-devel openssl-devel git wget
Note: Missing development libraries will produce configure-time errors like:
./configure: error: the HTTP rewrite module requires the PCRE library.
Always resolve these before continuing.
Nginx + RTMP: Source Build
Choose stable, not too old, not bleeding edge. 1.22.1 is a known baseline for compatibility.
export NGINX_VERSION=1.22.1
wget http://nginx.org/download/nginx-${NGINX_VERSION}.tar.gz
tar xf nginx-${NGINX_VERSION}.tar.gz
git clone https://github.com/arut/nginx-rtmp-module.git
cd nginx-${NGINX_VERSION}
./configure --with-http_ssl_module --add-module=../nginx-rtmp-module
make -j$(nproc)
sudo make install
Default install path remains /usr/local/nginx
. For most admins, systemd integration is manual. Consider creating a custom unit file later if persistent service management is required.
Core Configuration: Nginx RTMP Integration
Edit /usr/local/nginx/conf/nginx.conf
. Append the RTMP block and extend HTTP for HLS endpoint exposure.
rtmp {
server {
listen 1935;
chunk_size 4096;
application live {
live on;
record off;
# Optional: on_publish or on_play hooks for auth/naming
}
}
}
http {
include mime.types;
default_type application/octet-stream;
server {
listen 8080;
server_name _;
location / {
root html;
index index.html;
}
location /hls {
types {
application/vnd.apple.mpegurl m3u8;
video/mp2t ts;
}
root /tmp;
add_header Cache-Control no-cache;
# Note: /tmp is used for HLS segments. Consider storage with adequate IOPS.
}
}
}
Tip: Multiple application
blocks for parallel channels are supported, but plan chunk sizes based on bandwidth/load.
Launch Nginx
sudo /usr/local/nginx/sbin/nginx
Check endpoints:
ss -ntlup | grep 1935
# Example output:
# tcp LISTEN 0 128 0.0.0.0:1935 0.0.0.0:* users:(("nginx",pid=4152,fd=7))
For config reload, use:
sudo /usr/local/nginx/sbin/nginx -s reload
First Stream: OBS to RTMP Endpoint
Open broadcasters (OBS, ffmpeg) can push to this endpoint. With OBS Studio (v29+):
- Settings → Stream
- Service: Custom...
- Server:
rtmp://<server_ip>/live
- Stream Key: any identifier (e.g.,
internal
)
- Start Streaming
Monitor logs in /usr/local/nginx/logs/error.log
for connection details and errors, e.g.:
[info] 4206#0: connect: app='live' name='internal' args='' ...
Option: HLS Publishing for Browser Playback
Activate HLS (HTTP Live Streaming) for compatibility with HTML5 video players:
Append inside application live
:
hls on;
hls_path /tmp/hls;
hls_fragment 3s;
Prepare storage:
sudo mkdir -p /tmp/hls
sudo chown nginx:nginx /tmp/hls
sudo chmod 750 /tmp/hls
Reload and test. Watch via:
http://<server_ip>:8080/hls/<stream_key>.m3u8
Example with video.js:
<video id="example" controls>
<source src="http://server_ip:8080/hls/internal.m3u8" type="application/x-mpegURL">
</video>
Gotcha: Serving HLS via /tmp
is merely expedient for initial setups; production deployments should mount HLS segments on dedicated storage or RAM disk to avoid disk I/O contention.
Security and Observability
- Authentication: Nginx RTMP lacks out-of-box authentication. Use
on_publish
hooks with custom scripts or front it with additional auth middleware if necessary. - TLS: Consider terminating SSL using a reverse proxy (nginx mainline, Caddy, or HAProxy) if HLS will be public.
- Firewalls: Limit 1935/8080 to required IP scopes.
- Monitoring: Scrape and review
/usr/local/nginx/logs/
for failed publishes or unexpected load spikes; integrate with Prometheus exporters if feasible.
Note on CentOS 8 Lifecycle
CentOS 8 has reached EOL. Stream or Rocky Linux 8 are alternatives with better support for future migrations. This guide remains valid for legacy systems; for fresh deployments, map the steps to equivalent RHEL-compatible environments.
Practical Troubleshooting
- Failed to bind port 1935: Already in use. Validate no other media services are running.
- HLS segment errors: Permissions or storage exhaustion in
/tmp/hls
. - Dropped connections: Observe CPU and network usage; RTMP is sensitive to jitter—disable power-saving modes if on virtualized hardware.
Closing Remarks
This setup delivers a self-hosted, scalable streaming foundation. For multi-site delivery or high concurrency, layer a CDN or video edge cache after stabilizing the core pipeline. Adaptive bitrate (ABR) transcoding is possible but was omitted here—reference ffmpeg-on-the-fly workflows for that extension.
For long-term maintainability, script the build as an Ansible role or containerize the deployment. Gaps exist—expect to revisit auth, monitoring, and storage as real-world usage ramps.
Critically, you now own the pipeline end-to-end. No hidden costs, no vendor throttling—just system-level control, ready for workload growth.