--- title: "FastDL Setup: sv_downloadurl with Nginx or Apache" excerpt: "Serve maps, models and sounds over HTTP with sv_downloadurl, mirror the cstrike tree correctly, add bzip2 compression, and configure Nginx or Apache." category: "administration" tags: "fastdl, downloads" image: "" read_time: "8 min read" ---
Without FastDL, custom content downloads through the game server at a crawl — a few KB/s over the old UDP transfer, so a 20 MB map map-change empties the server. FastDL moves those transfers to a normal HTTP server via sv_downloadurl. The setup is not hard, but one detail — where exactly the URL points relative to the cstrike folder — accounts for the overwhelming majority of "FastDL not working" reports.
When a client joins and is missing a file the server references (a map, model, sound, sprite), it fetches that file over HTTP from sv_downloadurl instead of the game channel. Your web server just serves static files; the client reconstructs the path from the game-relative filename.
// server.cfg sv_allowdownload 1 sv_allowupload 1 sv_downloadurl "http://dl.example.com/cs"
sv_allowdownload 1 lets clients download at all; without it they join with missing textures and errors. Clients also need cl_allowdownload 1 on their end (the default), so you cannot fix a client that disabled it.
The engine builds the download URL as sv_downloadurl + the game-relative path, and that path includes the cstrike/ folder. So if a client needs cstrike/maps/de_rats.bsp and your sv_downloadurl is http://dl.example.com/cs, it will request:
http://dl.example.com/cs/cstrike/maps/de_rats.bsp
Therefore your web root must contain a cstrike/ directory that mirrors the server's, not the contents of cstrike/ dumped loose. The correct layout on the web host:
/var/www/fastdl/ <- sv_downloadurl points here (http://dl.example.com/cs)
cstrike/
maps/
de_rats.bsp
sound/
models/
sprites/
gfx/
overviews/
If you point the URL inside cstrike you get a doubled or missing segment and every download 404s. This is the single most common mistake; see FastDL not working for the diagnostic version.
The engine can fetch a bzip2-compressed copy and decompress it client-side, which roughly halves map transfer size. For each file, place a .bz2 alongside it:
cd /var/www/fastdl/cstrike/maps bzip2 -k de_rats.bsp # produces de_rats.bsp.bz2, keeps the original
The client requests de_rats.bsp.bz2 first and falls back to the plain file if it is absent. Compress the big content — maps, models, sounds. Keep the uncompressed files too; some clients still ask for them.
server {
listen 80;
server_name dl.example.com;
root /var/www/fastdl;
location / {
autoindex off;
# serve unknown types as raw bytes so .bsp/.bz2 download cleanly
default_type application/octet-stream;
types {
application/octet-stream bsp bz2 mdl spr wav;
}
}
}
Turn autoindex off so the mirror is not browsable, and make sure the server sends the files rather than trying to interpret them.
<VirtualHost *:80>
ServerName dl.example.com
DocumentRoot /var/www/fastdl
<Directory /var/www/fastdl>
Options -Indexes
Require all granted
AddType application/octet-stream .bsp .bz2 .mdl .spr .wav
</Directory>
</VirtualHost>
HTTPS works fine for sv_downloadurl and is worth it; just make sure the certificate is valid, because a TLS error makes the client silently fall back to the slow in-game transfer.
The web mirror is a separate copy of your content, and it drifts. Every time you add a map to the rotation or install a new model pack, the same files must land on the FastDL host, or clients download everything except the new content and join with missing textures. The lowest-friction fix is to make the mirror the same filesystem — bind-mount or symlink cstrike/maps, cstrike/sound and the rest into the web root — so there is only one copy to maintain. If the mirror is a different machine, script an rsync after every content change and re-run bzip2 -k on new files. A stale mirror is the second most common FastDL failure after the path mistake, and it is entirely self-inflicted.
cstrike/ level is missing or doubled in your web root. The client requests <url>/cstrike/maps/...; that exact path must resolve.sv_downloadurl is empty or unreachable and the server fell back to the UDP transfer. Test the URL in a browser.application/octet-stream and confirm the byte size matches.de_rats.bz2 instead of de_rats.bsp.bz2) or the web server refuses the extension. The compressed name is the full original name plus .bz2.sv_allowdownload 0 on the server, or the file is not actually on the mirror.Open the exact URL of one map in a browser and confirm it downloads with the right size:
http://dl.example.com/cs/cstrike/maps/de_rats.bsp
Then join the server from a client that does not have that map and watch it pull over HTTP in seconds rather than minutes. A clean test is to delete a map locally, connect, and confirm it re-downloads fast. Pair this with a well-ordered mapcycle so the mirror always holds every map you rotate.