Skip to content

Fast Emby / Jellyfin libraries with .strm (recommended)

If your library scans are slow, this is the setup you want.

Instead of mounting ClearStreamer and letting your media server read every file's header over the network, you sync a tiny local library of .strm shortcut files (plus the matching posters and artwork). Your media server scans that local folder — which is fast, because nothing crosses the network during a scan — and the actual video only streams from your CDN node when someone presses Play.

Why this is faster

A mounted library makes the scanner open every file over a high-latency link, so a full scan of a large catalog can take hours or days. With a local .strm library the scanner only touches small local files, so the same catalog scans in minutes. Playback is unchanged — it still streams direct from your CDN node.

How it works

ClearStreamer (origin)                Your server
──────────────────────                ─────────────────────────────
.strm + artwork  ──  rclone sync  ──▶  /library  ◀── Emby / Jellyfin scans this
(tiny files)                                            (fast, all local)

                                       Press Play ─▶ CDN node ─▶ video streams
                                                      (only the bytes you watch)
  • A .strm file is a one-line text file containing the streaming URL of one video. Emby, Jellyfin, Plex, and Silo all understand .strm natively and treat it like a playable video.
  • The .strm files and artwork are pre-generated for you on the ClearStreamer side, so your server never has to probe videos or download metadata to build the library.
  • Each .strm is addressed to your own CDN node as you download it, so playback goes straight to your node with nothing for you to configure or rewrite.
  • Only .strm stubs and images live on your disk. The video files never do — they stream from your CDN node on demand.
  • Alongside the stubs you get the full local metadata set: .nfo details, posters, fanart, season art, episode thumbnails, and subtitles. That's what lets your server build a complete-looking library with internet metadata turned off.

Mount or .strm? Pick one per library

The rclone mount and this .strm method are two ways to reach the same content. Use .strm when scan speed matters (large libraries, low-powered servers, high-latency links). Use a mount when you want the raw files to appear as real files on disk. Don't point two libraries at the same content with both methods — pick one per library.

Before you start

You need:

  • Your S3 endpoint, access key, and secret key (from your welcome email) and your whitelist IP already set — see Endpoint & credentials.
  • rclone installed on the server that runs Emby/Jellyfin — see S3 client examples.
  • Local disk space. The .strm stubs are tiny, but the artwork is not. Budget for the artwork, not the videos.

How much disk you need

The .strm files are a few hundred bytes each; posters, fanart, episode thumbnails, and subtitles are the bulk. The whole library is roughly 390 GiB across ~8.9 million files — a rounding error next to the video itself, but not nothing. Approximate size per section:

Section Size
television ~215 GiB
movies ~150 GiB
xxx ~14 GiB
sports ~3 GiB
courses ~2 GiB

If disk is tight, sync only the sections you want — each is a separate top-level folder, so you can pull movies and television and skip the rest. To check a subtree yourself:

rclone size --fast-list zendrive:strm-tree/movies

Plan for the file count, not the byte count

390 GiB is small, but 8.9 million files is not. The first sync takes hours, and it is limited by per-file round-trips rather than bandwidth. Make sure the destination filesystem is comfortable with millions of small files (ext4/XFS/ZFS are fine; avoid exFAT and most network shares).

Step 1 — Configure rclone

If you already mount ClearStreamer, you already have this remote and can skip ahead. Otherwise create ~/.config/rclone/rclone.conf (zendrive here is just the remote's name — use whatever you called yours):

[zendrive]
type = s3
provider = Other
access_key_id = YOUR_ACCESS_KEY
secret_access_key = YOUR_SECRET_KEY
endpoint = https://YOUR-ENDPOINT/
force_path_style = true

Verify it works:

rclone lsd zendrive:strm-tree

You should see one folder per section (courses, movies, sports, television, xxx).

Your .strm library location

The examples use the strm-tree bucket. If the bucket or path for your service differs, it's listed in your welcome email / client area — substitute it wherever you see zendrive:strm-tree.

Step 2 — Sync the .strm library to local disk

Pull the pre-generated .strm + artwork tree to a local folder. Use sync so your local copy stays an exact mirror (titles removed upstream are removed locally too):

rclone sync zendrive:strm-tree /library \
  --ignore-size \
  --fast-list \
  --transfers 64 --checkers 64 \
  --use-server-modtime \
  --max-delete 50000 \
  --retries 3 --low-level-retries 10 \
  --stats 2m --stats-one-line
Flag Why
--ignore-size Required — the sync fails without it. See the warning below.
--fast-list Reads the tree in batched LIST calls (one per ~1000 objects) instead of one call per directory — essential on a tree this large and deep. Costs ~1 KB RAM per object, so budget ~8 GB… see the note below if that's too much.
--transfers 64 --checkers 64 Millions of small files, so round-trips (not bandwidth) are the limit. Lower these if your server struggles.
--use-server-modtime Keeps each file's real date so "Recently Added" and date sorting work, and it's what the sync compares on. Never use --no-modtime — see the warning on the clients page.
--max-delete 50000 Safety cap: if something upstream looks catastrophically smaller than it should, the sync aborts instead of emptying your library.
--retries / --low-level-retries Survives transient network blips on a long sync.

--ignore-size is required, not optional

Each .strm file is personalised as it's delivered: the playback URL inside it is rewritten to point at your CDN node. That makes the delivered file a slightly different length than the catalog listing advertises, and rclone treats a size mismatch as a corrupt transfer:

ERROR : corrupted on transfer: sizes differ 142 vs 145

--ignore-size tells rclone to compare modification time instead of size, which is the correct check here. Leave it out and every .strm fails to transfer; use --size-only and every file re-downloads on every run, forever.

sync deletes local files that are gone upstream

rclone sync makes the destination match the source, which means it deletes local files no longer in the library. That's what you want for a mirror, but point it at a dedicated folder (like /library), never a folder that also holds your own files. If you'd rather only ever add files, use rclone copy instead.

If --fast-list uses too much memory

--fast-list holds the listing in RAM (~1 KB per object, so ~8 GB for the full library). On a smaller box, just drop the flag — rclone then walks the tree directory by directory, which uses far less memory at the cost of more requests and a longer run. Syncing one section at a time keeps both bounded.

The first sync takes hours — it's ~8.9 million files, and it's round-trip bound, not bandwidth bound. Later syncs only move what changed and finish far faster.

Keep it updated automatically (systemd)

Don't run the sync by hand — install it as a systemd timer so your library stays fresh on its own. This is the setup we recommend everyone use.

1. The sync script — wrapped in a lock so a long run can never be started twice:

#!/usr/bin/env bash
# /usr/local/bin/zendrive-strm-sync.sh
set -uo pipefail

LOG=/var/log/zendrive-strm-sync.log
DST=/library

# Refuse to start if the previous run is still going.
exec 9>/tmp/zendrive-strm-sync.lock
if ! flock -n 9; then
  echo "$(date -Is) SKIP: previous sync still running" >>"$LOG"
  exit 0
fi

echo "$(date -Is) START" >>"$LOG"
/usr/bin/rclone sync zendrive:strm-tree "$DST" \
  --ignore-size \
  --fast-list \
  --transfers 64 --checkers 64 \
  --use-server-modtime \
  --max-delete 50000 \
  --retries 3 --low-level-retries 10 \
  --stats 2m --stats-one-line \
  --log-file "$LOG" --log-level INFO
echo "$(date -Is) END rc=$?" >>"$LOG"

Make it executable: sudo chmod +x /usr/local/bin/zendrive-strm-sync.sh

2. The service — a one-shot job that runs that script. Run it as the same user your media server runs as so the files it writes are readable by Emby/Jellyfin, and keep it gentle on the box while a large sync runs:

# /etc/systemd/system/zendrive-strm-sync.service
[Unit]
Description=Sync ClearStreamer .strm library to local disk
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot

# Run as your media-server account so /library is readable by Emby/Jellyfin.
# rclone reads that user's ~/.config/rclone/rclone.conf.
User=emby
Group=emby

# Be a good neighbour: never starve the media server of CPU or disk I/O.
Nice=10
IOSchedulingClass=best-effort
IOSchedulingPriority=6

ExecStart=/usr/local/bin/zendrive-strm-sync.sh

# A full reconcile of the whole library legitimately runs for many hours.
# Set this too low and systemd kills the job mid-run — see the warning below.
TimeoutStartSec=20h

3. The timer — starts a run 20 minutes after boot, then 6 hours after each run finishes. Persistent=true means a run missed while the machine was off fires as soon as it's back:

# /etc/systemd/system/zendrive-strm-sync.timer
[Unit]
Description=Periodic ClearStreamer .strm library sync

[Timer]
OnBootSec=20min
OnUnitInactiveSec=6h
Persistent=true
RandomizedDelaySec=10min

[Install]
WantedBy=timers.target

Don't shorten the interval or the timeout

These two settings look conservative and aren't. A full reconcile of the library walks ~8.9 million objects and can run for many hours — long enough that an hourly timer would simply stack runs on top of each other, and a short TimeoutStartSec makes systemd kill the job partway through, every time, so the library silently stops updating.

OnUnitInactiveSec (not OnUnitActiveSec) is deliberate: it measures the gap from when the last run finished, so runs can never overlap regardless of how long one takes. RandomizedDelaySec just avoids every server hitting the endpoint on the same tick.

4. Enable it:

sudo systemctl daemon-reload
sudo systemctl enable --now zendrive-strm-sync.timer

5. Check it's working:

systemctl list-timers zendrive-strm-sync.timer     # when it last ran / runs next
systemctl status zendrive-strm-sync.service        # result of the last run
journalctl -u zendrive-strm-sync.service -n 50     # sync output
sudo systemctl start zendrive-strm-sync.service     # force a run right now

Match User= / Group= to your media server

Replace emby with the account Emby or Jellyfin actually runs as (Docker users: run the sync as the same UID/GID you pass the container, or write to a volume it can read). If rclone can't find its config, add --config /home/emby/.config/rclone/rclone.conf to the ExecStart line.

Picking up the new files

After each sync, your media server's own scheduled library scan finds the changes — no extra step needed. To have new content appear within seconds instead of on the next scan, pair this with ZenLocalPoller, which reacts to library changes as they happen.

Step 3 — Add the library in your media server

Point Emby or Jellyfin at the local folder — not at a mount. The settings below make the scan fast and stop the server from trying to fetch things over the network.

  1. Settings → Library → Add Media Library.
  2. Content type: Movies for a movies folder, Shows for TV. Add one library per content type, each pointing at the matching subfolder (e.g. /library/movies, /library/television).
  3. Folders: add the local path (e.g. /library/movies).
  4. Open the library's options and set:
    • Enable real time monitoring: OFF (you rescan after each sync instead).
    • Metadata downloaders (internet): uncheck them all. The artwork and .nfo files are already in the folder, so the server reads titles and images locally and never calls TMDb/TVDb.
    • Metadata readers: make sure Nfo is enabled and first.
    • Save artwork/metadata into media folders: OFF (the folder is a read-only mirror; let the sync own it).
    • Chapter image extraction: OFF (extracting chapter images would read the video over the network).
  5. Save. Emby scans the folder.
  1. Dashboard → Libraries → Add Media Library.
  2. Content type: Movies or Shows. Add one library per content type, each pointing at the matching subfolder (e.g. /library/movies, /library/television).
  3. Folders: add the local path (e.g. /library/movies).
  4. In the library settings:
    • Enable real time monitoring: OFF.
    • Metadata downloaders (Movie/Series/Season/Episode): uncheck them all so Jellyfin uses the local .nfo and artwork instead of the internet.
    • Metadata savers: leave Nfo as the reader; don't enable "Save artwork into media folders."
    • Chapter image extraction: OFF.
  5. Save. Jellyfin scans the folder.

Keep internet metadata off for the fastest, most reliable scans

The projection already ships posters, fanart, and .nfo details, so internet downloaders add nothing but network calls and rate-limit delays. If you specifically want the server to fetch extra metadata online you can turn a downloader back on, but expect slower scans.

Step 4 — Scan and verify

  • The scan starts automatically when you add the library, or trigger it from the library menu.
  • Play a title. It should start streaming from your CDN node within a second or two. Nothing was downloaded to your disk to make that happen — the .strm pointed the player straight at the CDN.

Large libraries: titles appear in minutes, the scan bar finishes later

On a very large library the items become browsable and playable within a few minutes, but the scan progress bar can keep moving for a good while afterward as the server finishes its internal bookkeeping over hundreds of thousands of items. This is normal — the library is fully usable while it finishes. It only happens on the first scan; later incremental scans are quick.

Troubleshooting

Symptom Fix
corrupted on transfer: sizes differ on every .strm You're missing --ignore-size. Add it to the sync command — see Step 2.
Every file re-downloads on every run You're using --size-only. Replace it with --ignore-size so rclone compares modification time.
Sync never finishes / library stops updating The run is being killed partway through. Check TimeoutStartSec is generous (20h) and that the timer uses OnUnitInactiveSec, not OnUnitActiveSec.
A title won't play Confirm your whitelist IP is current — playback still goes through your CDN node, which is IP-locked. See Updating your whitelist IP.
Scan is slow / server is probing videos You likely left an internet metadata downloader or chapter image extraction on, or you pointed the library at a mount instead of the synced folder. Recheck Step 3.
"Recently Added" / dates look wrong Make sure the sync uses --use-server-modtime (not --no-modtime).
Titles missing after an upstream change Re-run the sync (Step 2). Remember sync mirrors deletions; copy only adds.
Sync aborted with a max-delete error --max-delete did its job and stopped an unexpectedly large removal. Check the log to see what it wanted to delete before re-running.
Disk filling up Artwork is the bulk. Sync fewer sections, or drop the largest ones. Check with rclone size --fast-list zendrive:strm-tree/<section>.
out of memory during the sync --fast-list needs ~1 KB per object. Drop the flag, or sync one section at a time.

See also