Publishing a Camera Stream: RTSP and HLS Are Not the Same Edge
RTSP session control and HLS delivery need different public edges, even when both reach the same camera over a VPN. Choose the viewer model first.
You expose one camera port, point a player at it, and assume the internet problem is solved. Then a phone cannot play it, a browser cannot play it, and the camera's administration page sits one mistyped path away from the public edge. The delivery model you picked, RTSP or HLS, decides most of that before any firewall rule is written. The VPN only supplies the private leg.
The VPN fixes the source path, not the viewer path
A VPS joined to the camera LAN over a tunnel can reach the camera at its private address, and that is genuinely useful. It means the camera needs no inbound port of its own and the home router stays closed. It does not tell you what a viewer will speak. A desktop player, a browser, a phone and a smart TV support different protocols, and each one has its own expectations about TLS, authentication and codecs.
Here is the assumption worth discarding: publishing means making the source reachable. Publishing actually means giving a defined audience a supported way to consume the media. A published stream has a format, an audience and a credential; a reachable camera has none of those. Reachability is the part the tunnel solves, and the viewer entry point is the part you still have to design.
RTSP is a negotiation, not a single port
RTSP carries session control: a client asks for a description, sets up one or more media tracks, then plays. The transport for the media itself is negotiated during that setup, which is why the protocol is not well described by one forwarded TCP port. That negotiation is the part people miss when they compare firewall rules.
Depending on the endpoints, RTP may arrive on separate UDP ports or be interleaved on the control connection. A relay that forwards only 554 and assumes the rest will work tends to produce a session that connects, reports a track, and then shows nothing. Check the transport your camera actually negotiates before you write rules for it. A capture on the private side answers that in a minute, and guessing at it costs an evening.
For a small, known audience with compatible players, a deliberately configured RTSP relay can be the right answer. Constrain which source it may pull and require authentication at the relay. Write down the players you tested, because an RTSP stream that works in VLC may still fail in a browser.
HLS is a playlist plus resources
An HLS viewer fetches a playlist, then fetches the media segments that playlist names. The HLS specification defines that resource model, and the important operational detail is that playlists reference other URLs. Relative references resolve against the playlist location, so a proxy that rewrites paths has to rewrite them consistently.
If the camera already serves usable HLS, a public frontend can relay those playlist and segment requests across the tunnel. Watch for keys, redirects and URLs generated by the application. Any absolute URL pointing at a private host breaks the moment a viewer outside the LAN follows it. Relative paths are the ones that survive a proxy rewrite, so prefer them wherever you control the generated output.
If the camera speaks only RTSP, something has to convert the source into the viewer format. Repackaging and transcoding are different jobs: repackaging preserves the codec, while transcoding re-encodes it for compatibility, and only the second one costs serious CPU. Neither becomes necessary merely because a VPN is in the path.
Plan the uplink around where fan-out happens. One private ingest feeding a public media service behaves very differently from every viewer pulling its own stream across the tunnel. The second pattern usually ends in a saturated home connection and a camera that appears to fail at random.
Put authentication at the edge you publish
TLS, viewer authentication and access logging belong at the public entry point, not at the camera. That keeps credentials for the camera separate from credentials for the stream. It also gives you one place to revoke access when an audience member leaves. Putting those controls on the camera instead means every audience change becomes an administrative change to a device that should never be publicly reachable at all.
Keep the VPN and the camera's management permissions separate from the publishing path. A VPS that can reach the camera's web interface to pull a stream should not also be the machine exposing that interface. The audience should never learn the camera's address.
Then verify the two ends you actually built rather than assuming the chain works:
$ ffprobe -v error -rtsp_transport tcp rtsp://10.50.0.9:554/stream1
$ curl -sI https://stream.example.net/live/index.m3u8
HTTP/2 200
content-type: application/vnd.apple.mpegurl
A successful probe of the private source and a successful playlist fetch at the public edge prove different halves of the design. Test them separately. Playback from the same kind of client your audience uses is the only test that covers the whole chain.
What you end up with is a chain with a purpose at each link: private source, restricted ingest, chosen packaging, authorized viewer entry. None of those links is optional, and none is created by opening a port.