File Protection
Keep paid media from being downloaded or shared
If you sell content, or just don't want your media copied around the internet, this is the page to read. It explains every file protection feature in RamTV: what each one protects, what it costs your server, how it behaves with third-party storage, downloads and offline playback, and where its limits are.
You don't need all of it. Most sites pick one of the setups in the next section, turn on two or three settings, press a Test button and are done. The rest of the page explains why, so you can choose with your eyes open.
The short version
RamTV has three pieces that work together:
- Protect files keeps chosen media files in a private folder on your server. They can only be reached through short-lived links, which RamTV gives to visitors who are allowed to play or download them.
- Delivery mode decides who sends those private files to the visitor: PHP, Apache or nginx. PHP works everywhere; Apache and nginx are much lighter.
- HLS encryption cuts a file into small encrypted pieces. The pieces can sit on any storage, even a public one, because the key that unlocks them stays on your server and is only given to visitors who are allowed to play.
How each setup holds up:
| Your setup | Can someone download the file without paying? | Server load | Good for |
| Normal file, your server, Protect files Off | Yes, anyone who has the link | Lowest | Free content |
| Normal file, your server, Protect files On, PHP delivery | No | High: PHP streams every byte | Small sites and audio |
| Normal file, your server, Protect files On, Apache or nginx delivery | No | Low | Selling content from your own server |
| Normal file on third-party storage | Yes: storage links are public | None on your server | Free content you want off your server |
| HLS encrypted, on your server or third-party storage | The pieces can be copied, but they don't play without the key | Low | Selling content from third-party storage |
Recommended setups
- Free content only: leave Protect files Off. Use any storage.
- Selling content, files on your own server: set Protect files to Premium ( priced ) files only or All files, then switch delivery to Apache signed links or nginx secure_link and press Test.
- Selling content, files on third-party storage: turn on HLS Encryption. The encrypted pieces go to your storage, and the keys stay on your server.
- Mixed: HLS for large videos, Protect files for everything else. The two work side by side.
Protect files
Open Admin → Setting → Files → Upload Setting. The Audio group and the Video group each have a Protect files setting with three choices:
| Choice | What it protects |
| Off | Nothing. Files are stored and served normally. This is the default. |
| Premium ( priced ) files only | Media files of items that have a price at the moment the file is processed. With the TV Shows plugin, an episode counts as priced when the episode itself or its TV show has a price. |
| All files | Every uploaded audio or video source, priced or free. Use this when access depends on something other than a price, such as a subscription plan or a user role. |
Protected files are moved into a private folder, files/protected, on your own server. Nobody can open a file there directly. A visitor gets a link only when RamTV has checked that they can play or download the item, and the link stops working after a while.
Good to know
- The decision is made once, when the file is processed. If you add a price to an item later, the files it already has stay unprotected. Changing the setting later doesn't move files that were already uploaded, in either direction. Files that are already protected stay protected and keep playing.
- Only media sources are protected. Covers, avatars, ads videos and page-builder media are never moved into the private folder, whatever you choose.
- Previews are never protected. They are meant to be free. Trailers are never protected either.
- HLS encrypted files are not moved. Their protection is the key. See the HLS section below.
- Protected files always stay on your server, even if you have set a third-party storage for audio or video. Plan your disk space for that.
Different rules for different item types
Further down the same Upload Setting page there is a group for every item type that has media sources, named after the item and the file, for example “… Source - File - video”. Each group has its own Protect files rule. Leave it on Default to follow the Audio or Video setting, or pick a different choice for just that item type.
How protected files reach visitors
Once a file is protected, something has to check each visitor's link and send the file. You choose what in Admin → Setting → Files → Upload Setting → Protected files → Serve protected files with.
| Mode | Who sends the file | Server needs | Stream links last | Download links last |
| PHP ( protector.php ) | PHP, for the whole length of the stream | Nothing extra. Works everywhere. | 20 minutes, extended while playing. Locked to the visitor's browser. | 24 hours. Locked to the visitor's IP address and browser. |
| Apache signed links | Apache | Apache 2.4.26 or newer with mod_rewrite and mod_version, the shipped .htaccess file, and the cronjob | Twice the length of the media, at least 3 and at most 12 hours | 24 hours |
| nginx secure_link | nginx | nginx's secure_link module (included in most builds) and the rules below | Twice the length of the media, at least 3 and at most 12 hours | 24 hours |
PHP ( the default )
protector.php checks every request and streams the file itself. It needs no setup and works on every host. The cost: every visitor who is playing something holds one PHP worker for as long as they play. That's fine for audio and a handful of viewers. With long videos and many viewers at once, PHP runs out of workers and the whole site slows down. When that happens, switch to Apache or nginx.
Apache signed links
RamTV writes a tiny token file for each link, and Apache checks it and sends the file itself, with no PHP involved. It's as fast as serving a normal file.
- The rules are in
files/protected/.htaccess, which ships with RamTV. Nothing needs to be added to your Apache configuration. - Links end on the first cronjob run after they expire, because the cronjob deletes expired tokens every minute. Your cronjob must run every minute (see Server setup). If the cronjob stops, links stop expiring.
- If Apache can't check a link itself (an older Apache, or a host that disables the needed module), the same link falls back to
protector.phpautomatically. Nothing breaks; you just don't get the speed. The Test button tells you which one answered. - Signed links are not tied to an IP address or browser. That's what lets Apache serve them without PHP. They are short-lived instead.
For reference, this is the shipped files/protected/.htaccess. If it goes missing, restore it exactly:
Options -Indexes
RewriteEngine On
RewriteRule ^\.sl(/|$) - [F,L]
<IfModule mod_version.c>
<IfVersion >= 2.4.26>
RewriteCond expr "%{REQUEST_FILENAME} . '?' . %{QUERY_STRING} =~ m#^(.+/protected)/[^?]*\?(?:.*&)?t=([a-f0-9]{32})(?:&|$)# && -f $1 . '/.sl/' . md5( $2 . %{REQUEST_URI} )"
RewriteRule ^ - [L]
</IfVersion>
</IfModule>
RewriteCond %{QUERY_STRING} (^|&)t=[a-f0-9]{32}(&|$)
RewriteRule ^ ../../protector.php [L,QSA]
RewriteRule ^(.*)$ ../../protector.php?path=$1 [NC,L,QSA]nginx secure_link
nginx checks each link's signature and expiry itself and sends the file. There's no token file and no cronjob involved.
- Select nginx secure_link and save. RamTV generates a secret the first time.
- The setting's tip now shows your nginx configuration with your secret filled in. Copy it into your site's
serverblock, above the PHPlocation, and reload nginx. - Press Test.
The configuration looks like this, with YOUR_SECRET replaced by your own secret:
location ^~ /files/protected/ {
if ($arg_s = "") { rewrite ^/files/protected/(.*)$ /protector.php?path=$1 last; }
secure_link $arg_s,$arg_e;
secure_link_md5 "$secure_link_expires$uri YOUR_SECRET";
if ($secure_link = "") { return 403; }
if ($secure_link = "0") { return 410; }
}
location ^~ /files/hls_keys/ {
location ~ "^/files/hls_keys/(?<hls_key_name>[a-f0-9]{32})\.key$" { rewrite ^ /hls_key.php?name=$hls_key_name last; }
deny all;
}- The second block protects HLS keys. Every nginx site needs it, whatever delivery mode you use, as soon as HLS Encryption is on.
- If you copied this block from an earlier version of this page, replace it with the one above. The older rule never passed the key's name on to
hls_key.php, so HLS items could not play on nginx. - An expired link answers 410 and a forged one 403.
- Changing the secret ends every link already issued. People who are playing something just press play again.
- nginx never reads
.htaccessfiles. On nginx these rules are the only thing keeping both folders private. Thefiles/protectedrule is also needed in PHP mode. See Nginx for the rest of the rules RamTV needs.
The Test button
The Test button next to Serve protected files with tests the mode that's selected, before you save. It creates a throwaway file in the protected folder, fetches it through a real link, and then tries a forged link. You'll see one of these results:
- The web server checked the link and served the file itself: all set, save.
- The link works, but protector.php served it: safe, but still going through PHP. On Apache, check your version and modules. On nginx, check that the rules are in the right place and that nginx was reloaded.
- A tampered link was not refused: your protected folder is not private. Don't use that mode until the test passes.
- This server could not reach its own address: some hosts block a site from calling itself. Test by opening a protected item in your browser instead.
HLS encryption
HLS Encryption (Audio and Video groups in Admin → Setting → Files → Upload Setting) uses FFmpeg to cut each file into small pieces, 20 seconds for audio and 60 seconds for video, and locks every piece with AES-128 encryption. RamTV's player downloads the pieces and unlocks them as it plays. The pieces are useless without the key. Set up FFmpeg first.
How the key is protected
- Every file gets its own key, kept on your server in
files/hls_keys. The key never goes to third-party storage. - The key is only handed out to a visitor who was just allowed to play or download the item, and only to the same IP address they asked from. A copied key link doesn't work for anyone else.
- Access lasts 3 hours and is extended automatically while the visitor keeps playing. Access for a download lasts 24 hours.
- The player asks for the key again for every piece. When access ends, playback stops within about one piece.
Why HLS is the answer for third-party storage
The encrypted pieces and the playlist can go to any storage, including FTP and S3-compatible buckets that serve files publicly. Anyone can download the pieces, and nobody can play them without the key from your server. You get third-party bandwidth and protection, with nothing streamed through PHP.
The storage has to allow your site to load the playlist and pieces from the browser (CORS). See Storage. Some providers, such as Bunny, need .m3u8 and .ts allowed explicitly.
Things to know before you turn it on
- Set your final address first. Each playlist remembers your site's address, including
http://orhttps://, at the moment the file was encoded, and loads its key from there. If you later change domain, keep the old domain pointing at the same site. If you move from http to https, browsers will block the oldhttp://key addresses. Put your site on its final domain, with HTTPS, before you encode. - Back up
files/hls_keys. If you lose the keys, every HLS file made with them becomes unplayable, and nobody can recover them. Copy the folder whenever you move servers. - Only new uploads are encrypted. Turning HLS on doesn't re-encode existing files.
- HLS files can't be downloaded as normal files. Visitors can still save them for offline playback inside your site (see below). To also offer a normal file, tick HLS Encryption - Keep real files for download. That keeps an unencrypted copy, which uses more storage. The copy is protected only if Protect files applies to it.
- Shared IP addresses: people behind the same IP address (an office, a school, some mobile networks) share that address. Someone there who has been given the key could pass it to someone else behind the same address. This is rarely a problem in practice.
- Changing networks: if a visitor's IP address changes while playing (for example, going from Wi-Fi to mobile data), playback may stop. Pressing play again fixes it.
- Casting: casting to a device on the same network works, because the device uses the same public IP address.
- Embeds: HLS items can be embedded on other sites and still play.
Third-party storage and protection
FTP and S3-compatible storages serve files from public addresses. That's what makes them cheap and fast, and it also means RamTV can't put a check in front of them. You set buckets to public when you connect them (see Storage).
- Normal files on third-party storage are public. Anyone who has a file's address can download it. The address can be found in the browser's developer tools. That's fine for free content. For paid content it's your call; we don't recommend it.
- Protect files overrides the storage choice. A file that gets protected is kept on your own server, even if audio or video is set to go to a storage. Your storage won't receive those files.
- To protect files and keep them on a storage, use HLS. This is the setup we recommend.
RamTV doesn't use the storage provider's own signed links (such as S3 pre-signed links). HLS is the supported way to protect files on a storage.
Rentals
A rental gives access for a limited time. See Purchases & Rentals for how to set it up. Here's what happens to the file when time runs out:
| Setup | When the rental ends |
| HLS encrypted | Key access ends exactly when the rental does. Playback stops within one piece, about a minute. |
| Protected, Apache or nginx delivery | Links are never issued past the rental's end. Playback stops when the link expires. |
| Protected, PHP delivery | No new links are issued. A stream that is already playing can continue until the viewer stops or reloads. |
| Not protected | The play button goes away, but anyone who kept the file's address can still open it. |
Rentals can't be downloaded, because a saved file would outlast the rental.
Downloads and offline playback
Visitors whose role allows downloads can save items for offline playback inside your site. This works with protected files in every delivery mode, and with HLS encrypted files. The saved copy lives in the browser's storage and plays in your site's player. It isn't a file on the visitor's computer.
- Your site needs HTTPS. Browsers only allow offline storage on secure sites.
- The visitor must have played something online in that browser at least once, so the player itself is stored for offline use.
- Saved items keep playing when the connection drops. Once the visitor has opened your site online in that browser, it also opens with no connection at all: it starts in offline mode, where saved items can be played.
- Download links for protected files last 24 hours.
- A download of a protected file that stays paused for longer than that can't be resumed. Cancel it and download the item again.
- Protected normal files can't be embedded on other sites; HLS items can.
Cloudflare and other proxies
Download links in PHP mode and HLS keys are tied to the visitor's IP address. Behind Cloudflare's proxy, every visitor seems to come from a Cloudflare address, and those addresses change between requests. If Cloudflare's proxy is on, turn on Cloudflare support (cf_cache in config_user.php, see Cloudflare). Without it, HLS playback and downloads can fail.
- Don't add Cloudflare rules that cache
/files/protected/,/files/hls_keys/,protector.phporhls_key.php. A cached copy would be served to everyone. - Behind any other proxy or load balancer, all visitors may share one address. Things keep working, but IP locking protects less.
What protection can't do
- Recording: anyone who can play can record the screen or the sound. Nothing on the web stops that.
- People who paid: someone with access can save what they play while their access lasts (a download, or a key together with the pieces) and share it. Short link lifetimes and IP binding make this harder, not impossible.
- Kept download copies: a real file a visitor downloaded is theirs to keep. If that's a concern, don't allow downloads for that role or item.
- DRM: RamTV doesn't include Widevine, FairPlay or PlayReady. Those need a licence from each vendor, a paid key service and a different player setup. They also prevent copying the stream, but can't prevent recording either.
For most sites, Protect files with Apache or nginx delivery, or HLS encryption, is the right balance of protection, speed and cost.
Moving servers, backups and updates
- Back up and copy
files/protectedandfiles/hls_keysalong with the rest of your files. Losinghls_keysmakes HLS files unplayable. - Make sure
files/protected/.htaccessandfiles/hls_keys/.htaccesscome along. Turn on hidden files in your FTP client. - PHP must be able to write in
files/protected, because Apache mode keeps its tokens in a.slfolder inside it. - Keep your cronjob running every minute if you use Apache signed links.
- If you change your domain or move to HTTPS, read the HLS notes above first.
- After a move, press Test in the Protected files group, and play one protected item and one HLS item.
Troubleshooting
| What you see | Likely cause | What to do |
| A protected file opens directly by its address | The .htaccess in files/protected is missing, or you're on nginx without the rules | Restore the file or add the nginx rules, then run Test |
| Test says protector.php served the link | Apache too old or missing mod_version, or nginx rules not loaded | Upgrade or enable the module, or fix and reload nginx. Or stay on PHP |
| Apache links keep working long after they should expire | The cronjob isn't running | Fix the cronjob and check Run logs |
| Site slows down when many people watch | PHP delivery holding a worker per stream | Switch to Apache or nginx delivery, or use HLS |
| HLS items don't play, or stop partway | The key was refused: IP changed, Cloudflare support off, or the nginx key rule is missing or outdated | Turn on cf_cache behind Cloudflare, add or replace the nginx hls_keys block shown above, then press play again |
| Old HLS items stopped after a domain or HTTPS change | Playlists load keys from the old address | Keep the old domain pointing at the same site |
| HLS won't load from storage | CORS isn't allowed on the bucket | Allow your domain for .m3u8 and .ts on the storage |
| Nothing plays after moving servers | files/hls_keys or the .htaccess files weren't copied | Copy them from your backup |
| A priced item's file isn't protected | The price was added after the file was processed | Upload the file again, or accept it as unprotected |
| Saved item won't play offline | Not on HTTPS, or the player was never loaded online in that browser | Use HTTPS; play something online once |
Checklist
Related pages: Files, Storage, FFmpeg, Nginx, Cloudflare.