Configuring Varnish on systemd in 2026: drop-in overrides, not varnish.params

Back in 2015 I wrote about running Varnish 4.x on systemd , when the RedHat-family packages moved your startup options out of /etc/sysconfig/varnish and into a new /etc/varnish/varnish.params file that the unit sourced.

That file is now legacy too. The modern packages don’t ship a params file at all. They put every startup option directly in the service unit’s ExecStart, and the right way to change them is a systemd drop-in override. Here’s how it looks in 2026.

I tested this on Debian 13’s varnish package, which is 7.7.0. The distro packages lag the project (Vinyl/Varnish is on 9.x upstream now), but the systemd integration is identical across those versions, so this all applies whether your box runs 7.x, 8.x or 9.x.

There is no varnish.params anymore#

First, let’s kill the old mental model. On a current Debian or Ubuntu, none of the files the old posts told you to edit exist:

$ ls -l /etc/varnish/varnish.params
ls: cannot access '/etc/varnish/varnish.params': No such file or directory

$ ls -l /etc/default/varnish
ls: cannot access '/etc/default/varnish': No such file or directory

No params file, no defaults file. So where do the startup options live now?

In the unit itself#

They’re baked into ExecStart in the shipped service unit. Read it with systemctl cat varnish:

$ systemctl cat varnish
# /usr/lib/systemd/system/varnish.service
[Unit]
Description=Varnish Cache, a high-performance HTTP accelerator
Documentation=https://www.varnish-cache.org/docs/ man:varnishd

[Service]
Type=exec

LimitNOFILE=131072
LimitMEMLOCK=85983232

# Enable this to avoid "fork failed" on reload.
TasksMax=infinity

LimitCORE=infinity

ExecStart=/usr/sbin/varnishd \
          -F \
          -a :6081 \
          -T localhost:6082 \
          -f /etc/varnish/default.vcl \
          -S /etc/varnish/secret \
          -p feature=+http2 \
          -s malloc,256m
ExecReload=/usr/share/varnish/varnishreload
Restart=on-failure
KillMode=mixed
ProtectSystem=full
ProtectHome=true
PrivateTmp=true
PrivateDevices=true

[Install]
WantedBy=multi-user.target

A few things worth pointing at:

  • Type=exec and -F mean varnishd runs in the foreground and systemd supervises it directly.
  • The default listen port is :6081, not :80. The packaged unit deliberately does not grab port 80, so it won’t fight your webserver on a fresh install.
  • ExecReload runs /usr/share/varnish/varnishreload, the script that loads a new VCL without a restart. More on that below.
  • TasksMax=infinity with that comment is a real scar: without it, a reload can hit the default task limit and fail with “fork failed”. Leave it alone.
  • The Protect* and Private* lines are systemd sandboxing the packagers added over the years.

The wrong way and the right way to change it#

The wrong way is to edit /usr/lib/systemd/system/varnish.service directly. The next package update overwrites it and your changes vanish.

The right way is systemctl edit varnish, which creates a drop-in file under /etc/systemd/system/varnish.service.d/. systemd merges it on top of the shipped unit, and it survives upgrades.

There’s one trick with overriding ExecStart: you can’t just add a second one, you have to clear it first with an empty ExecStart= and then set your own. Here’s a drop-in that moves Varnish onto port 80 and bumps the thread pools:

# /etc/systemd/system/varnish.service.d/override.conf
[Service]
ExecStart=
ExecStart=/usr/sbin/varnishd \
          -F \
          -a :80 \
          -T localhost:6082 \
          -f /etc/varnish/default.vcl \
          -S /etc/varnish/secret \
          -s malloc,1g \
          -p thread_pool_min=50 \
          -p thread_pool_max=2000

After writing a drop-in you reload the systemd manager and restart the service:

$ systemctl daemon-reload
$ systemctl restart varnish

Don’t take my word that it worked. Ask systemd what ExecStart it’s actually using:

$ systemctl show varnish -p ExecStart
ExecStart={ path=/usr/sbin/varnishd ; argv[]=/usr/sbin/varnishd -F -a :80 -T localhost:6082 -f /etc/varnish/default.vcl -S /etc/varnish/secret -s malloc,1g -p thread_pool_min=50 -p thread_pool_max=2000 ...

The override won, the shipped -a :6081 is gone. And it’s listening on 80 now:

$ ss -ltnp | grep -E ':80 |:6081 '
LISTEN 0  1024  0.0.0.0:80  0.0.0.0:*  users:(("cache-main",pid=1285),("varnishd",pid=1265))

Port 6081 is no longer bound. The override is live.

Graceful reloads with varnishreload#

The thing you do most often isn’t a restart, it’s reloading a changed VCL. A restart drops your cache (and briefly your traffic); a reload compiles the new VCL, loads it, and switches over while the old one keeps serving until it’s safe. That’s what ExecReload is for:

$ systemctl reload varnish
$ journalctl -u varnish | tail -3
varnishd[1265]: CLI ... Rd vcl.use reload_20260607_135619_1738
varnishd[1265]: CLI ... Wr 200 VCL 'reload_20260607_135619_1738' now active
varnishreload[1738]: VCL 'reload_20260607_135619_1738' now active
systemd[1]: Reloaded varnish.service.

varnishreload loaded a new VCL under a timestamped name and made it active. No restart, no dropped cache, no gap in service. Edit your default.vcl, run systemctl reload varnish, done. (Run varnishd -C -f /etc/varnish/default.vcl first to catch syntax errors before the reload, since a broken VCL won’t load.)

How this looks in production#

At Oh Dear this is exactly how the status-page cache is configured. We don’t touch the shipped unit; everything lives in drop-ins. The ExecStart override pins the tuning we want:

# /etc/systemd/system/varnish.service.d/override.conf
[Service]
ExecStart=
ExecStart=/usr/sbin/varnishd \
  -F \
  -a 127.0.0.1:6081 \
  -f /etc/varnish/default.vcl \
  -s malloc,256m \
  -s Transient=malloc,64m \
  -p default_grace=3600 \
  -p http_resp_hdr_len=32768 \
  -p http_resp_size=65536 \
  -p thread_pool_max=500 \
  -p thread_pool_min=50

Varnish listens on loopback only (127.0.0.1:6081) because Caddy terminates TLS in front of it and proxies to it locally, the setup that absorbed a million-requests-a-minute DDoS on our status pages . The bumped http_resp_hdr_len and http_resp_size give headroom for large response headers, and default_grace=3600 matches the grace window the VCL relies on to keep serving stale content when the backend is unhappy.

Because the unit is just a systemd service, you also get systemd’s resource controls for free, in a second drop-in:

# /etc/systemd/system/varnish.service.d/memory.conf
[Service]
MemoryHigh=400M
MemoryMax=500M

That caps the whole cgroup, so a runaway Varnish can’t eat the box even though it’s told to use a 256MB malloc store. You can split config across as many .d/*.conf files as you like; systemd merges them all.

A note for RedHat-family boxes#

If you’re on Rocky/Alma/RHEL, the shape is the same: the unit ships with the options in ExecStart, you override with systemctl edit, and you reload with varnishreload. The /etc/varnish/varnish.params file from my 2015 post may still be present on older RedHat-family packages, but the cross-distro modern pattern is the drop-in, and that’s what I’d reach for on any current box.

Validated on Debian 13 with its varnish 7.7.0 package, running under systemd as PID 1.