Fix signage CMS media downloads failing behind a reverse proxy

8 min read · Published November 5, 2026

Media downloads that 404 behind a reverse proxy come from two places. If the download URLs in the RequiredFiles XML start with the wrong host, the proxy is dropping the Host header; add ProxyPreserveHost On. If the URLs are right and the Library page download fails too, set the CMS SENDFILE_MODE setting to the web server that serves the library: Apache for a Docker install, Nginx otherwise.

When every media download from a self-hosted CMS comes back as 404 not found behind a reverse proxy, the fault sits in one of two places, and the first thing to check is concrete: open the RequiredFiles XML and compare each download URL with the CMS address the player connects to. A link beginning with http://127.0.0.1 means the proxy is not passing the client's Host header, and the fix is ProxyPreserveHost On in the Apache virtual host. A link that matches the CMS address, with the same file returning 404 from the Library page in a browser, points at the CMS's SENDFILE_MODE setting in the settings table of the database: Apache for a Docker install, Nginx for a manual install under Nginx. For the player half of the setup, start with how to choose a digital signage player.

What each symptom points to

Match what you see against this list, then work down the checks in order.

  • The schedule loads but no file does. The player fetches its schedule over a working link, then fails on every media URL. The log fills with entries such as LoadNewScheduleLayout invalid alongside 404 not found responses from the server. Both checks below apply.
  • The download URLs start with the wrong host. The RequiredFiles XML lists links beginning with http://127.0.0.1, or with any host other than the CMS address. The Host header is not reaching the CMS; on Apache, ProxyPreserveHost On is missing from the virtual host.
  • The URLs are right and the browser fails too. The same file returns 404 from the Library page in a browser. The proxy is carrying CMS traffic, so the fault is in how the CMS serves files. Go to SENDFILE_MODE.
  • Upload works but download never does. The file sits in the library directory on disk, shared/cms/library on a Docker install, and still nothing downloads. The file is there; the serving mode is wrong.
  • Messaging connects but downloads fail. The player's push messaging link on port 9505 comes up while downloads fail, so the proxy forwards what it was given. Compare the download request next.
  • The proxy redirects HTTP to HTTPS itself. A RedirectMatch that bounces plain traffic before the CMS sees it is worth removing, but expect the host header and SENDFILE_MODE to be where the fault lives.

Check the download URLs the CMS hands out

Every media file reaches the player by URL. The CMS answers the player's RequiredFiles call with a list of files and a download link for each, and the player fetches each file from that link over HTTP or HTTPS. That makes the link text the first thing to inspect, and it is readable without any packet capture.

Put a display into audit mode, let it call in, and open the XML generated for the RequiredFiles call in that display's audit entries. Compare every download URL with the CMS address the player is set to connect to, scheme, host and port. If the CMS lives at https://cms.example.org, every download link should begin with https://cms.example.org. A quicker version of the same test is to copy one link into a browser, the way you would check a database image served through an HTTP endpoint.

http://127.0.0.1 is the usual wrong host. It is the server's own loopback address, and it is the signature of a Host header that is not being passed through the proxy.

On Apache the missing line is ProxyPreserveHost On in the virtual host that fronts the CMS. With it on, Apache forwards the client's original Host header to the CMS; with it off, the CMS sees the host written on the ProxyPass line. Add the line, reload Apache, and read the RequiredFiles XML again. For a complete virtual host of this shape, configuring a web admin behind an Apache proxy is a worked example of an application published the same way.

Two neighbouring lines belong in the same file. RequestHeader set X-Forwarded-Proto "https" tells the CMS that TLS ends at the proxy; keep it while you terminate HTTPS there. Take the HTTP-to-HTTPS RedirectMatch out of the proxy and let the CMS handle the redirect with its Force HTTPS option, in the main CMS settings or the display settings profile. And for a Nginx front end instead of Apache, the same job is done by that server's host-header pass-through setting; configuring subdomains behind an NGINX proxy is a worked example of publishing an application that way.

Then test the same file from the Library page

When the URLs are correct and downloads still fail, stop working on the proxy. The CMS's own pages load through it, so the proxy is carrying CMS traffic; the question becomes whether the CMS can serve a file at all.

Open the CMS Library, find one media file, and download it in a browser. The browser takes the player out of the path completely. Confirm the file exists on disk first: on a Docker install, uploaded files land in shared/cms/library. A freshly uploaded file that never appears there points at the storage mapping between the CMS and its library volume, and no amount of proxy tuning will fix that.

If the file is on disk and the browser still gets 404 not found, read SENDFILE_MODE from the settings table of the database. The setting is hidden in the CMS settings pages by default, so the database is where it is read and changed. It names the server program the CMS hands downloads to, and it has to match the one actually serving the library: Apache for a Docker install, Nginx for a manual install under Nginx. A Docker install carrying Nginx in that setting will return 404 for library downloads even with a correct proxy config. Set the value to the serving program and repeat the Library download; when the browser receives the file, the players receive it on their next call-in.

Example: a Docker CMS behind an Apache proxy that 404s every download

The inputs, each read from your own install:

  • CMS address the players connect to: https://cms.example.org, taken from the player's connection settings.
  • Symptom: layouts log as invalid, the server reports 404 not found for media, and the messaging link on port 9505 connects.
  • Proxy: an Apache virtual host on port 80 in front of the CMS, already carrying ProxyPreserveHost On, with timeout=5 on the proxy line, taken from the virtual host file.
  • Install type: Docker, confirmed by the deployment; an uploaded test file is visible in shared/cms/library.

The rules, in words. First, every download URL in the RequiredFiles XML must start with the same scheme, host and port as the CMS address the player connects to; any other host in that position means the proxy dropped the Host header. Second, a file the browser cannot download from the Library page is a server-side fault, because the CMS pages themselves come through the proxy fine. Third, a file that sits in the library directory and still 404s points at SENDFILE_MODE.

The checks, in order:

  1. Audit mode. Every download URL in the RequiredFiles XML starts with https://cms.example.org. Compare with the CMS address, https://cms.example.org: equal, so the Host header passes and ProxyPreserveHost On stays as it is. Check one clears.
  2. Library page in a browser. The same file returns 404 not found. The proxy delivers CMS pages normally, so it is ruled out for this fault; the failure moves to the CMS server.
  3. Disk. The uploaded test file is present in shared/cms/library, so storage is not the fault either.
  4. Settings table. SENDFILE_MODE reads Nginx on a Docker install. Docker installs serve the library with Apache, so the value names the wrong server program.
  5. Fix and re-test. Set SENDFILE_MODE to Apache and repeat the Library download. The browser receives the file this time.

Result: on the player's next call-in, the CMS hands out the same URLs and the player now downloads the files behind them. The invalid layout entries stop and the schedule plays with its media in place.

Decision: fix the proxy whenever the host in the XML differs from the CMS address, and fix SENDFILE_MODE whenever a browser 404s a file sitting in the library directory of a CMS whose pages load normally. The threshold is exact: https://cms.example.org against https://cms.example.org passes check one, and anything else in the host position fails it.

Proxy lines worth cleaning up while you are in the file

  • Remove the RedirectMatch permanent bounce from HTTP to HTTPS. Forward plain and TLS traffic to the CMS and let the CMS redirect, with its Force HTTPS option in the main settings or the display settings profile.
  • Take the timeout=5 off the proxy line. Five seconds is far too short for some CMS calls, including library operations. Drop the explicit timeout or set it well above the longest call the CMS makes.
  • Keep RequestHeader set X-Forwarded-Proto "https" while TLS ends at the proxy, so the CMS sees the front connection as HTTPS.
  • Carry ProxyPreserveHost On in every virtual host that fronts the CMS, not only the one being edited that day.

The fix in order

Recommendation: Put a display into audit mode and read the RequiredFiles XML before changing anything. If the download URLs start with any host other than the CMS address, add ProxyPreserveHost On to the Apache virtual host, or its header pass-through equivalent on Nginx, and stop there. If the URLs are right, download the same file from the Library page in a browser; if that returns 404 not found while the file sits in shared/cms/library, set SENDFILE_MODE in the settings table of the database to the server that serves the library: Apache for a Docker install, Nginx for a manual install under Nginx. Then remove the proxy's HTTP-to-HTTPS redirect and its 5-second timeout.

You are finished when the Library download returns the file in a browser and the player's log stops filling with invalid layout entries. Media that failed to reach players while the site was live deserves one playback check before the next content push; testing video bitrate before you deploy covers the common failure there.

If a screen has no CMS role at all, we sell displays whose free player runs without a network: images and MP4 video from microSD or USB, no subscription, with an optional HTTPS sync that pulls a playlist from your own web server on a timer. The player category lists what ships with it.

Questions

The CMS address includes a custom port. Why do downloads still fail?

In one documented failure, the CMS served its RequiredFiles download URLs without the custom port the players were connecting on, and the player rejected the link even though the CMS itself was reachable. If your CMS address includes a port, open the RequiredFiles XML and check that every download URL carries the same host and port. A URL missing the port is the same class of fault as a wrong host: the player is sent somewhere the CMS does not serve.

Where is SENDFILE_MODE changed?

It is hidden in the CMS settings pages by default, so it is read and changed in the settings table of the database. Take a database backup before editing a table by hand, then set the value to the web server that actually serves the library files: Apache for a Docker install, Nginx for a manual install under Nginx. Repeat the Library page download afterwards to confirm the change took.

Is there anything to change on the players themselves?

Nothing on the player fixes this fault. The download URL is handed to the player by the CMS, so a wrong host or a failing download is corrected on the server and the proxy. Leave the player's CMS address alone; the passing condition is that the URLs in the RequiredFiles XML start with that same address.

Planning information, not legal or engineering advice. Rules differ by place: check codes and permits with your local authority and your licensed trades.

Keep reading

Build your signAsk for a quote