19.7 Overriding Vendor Units with systemctl edit and Drop-Ins
Right, so you’ve installed some package—let’s say nginx—and its maintainers have kindly provided a systemd service unit for you. It’s fine. It works. But it’s not yours. Maybe you want to add an environment variable, tweak a restart policy, or run it as a different user. Your first instinct might be to just copy /usr/lib/systemd/system/nginx.service to /etc/systemd/system/ and go to town.
Don’t. That’s how you create a maintenance nightmare. The next time the nginx package updates, your custom version is now a time bomb, completely oblivious to any security or functionality changes the vendor might have made. You’ll be left with a service file that’s both outdated and out of sync.
systemd, for all its… opinions, has an elegant solution for this: drop-in snippets. The idea is brilliant in its simplicity. Instead of replacing the entire unit file, you create a small fragment that overrides only the specific directives you want to change. The main unit file remains pristine and owned by the package manager, and your customizations live separately, applied on top at runtime. It’s the best of both worlds.
How systemctl edit Creates a Drop-In
The easiest way to do this without memorizing paths is to use systemctl edit. This command is your new best friend. Let’s say you want to modify the nginx service. You’d run:
sudo systemctl edit nginx.service
This command isn’t magic, though it feels like it. It does a few key things for you:
- It creates the directory
/etc/systemd/system/nginx.service.d/if it doesn’t exist. - It creates a new file inside that directory, typically named
override.conf. - It drops you into your
$EDITORwith that file open.
Now, here’s the critical part: when systemd loads the nginx.service unit, it will automatically read all .conf files in that .d directory, in alphabetical order, and apply the directives within them after the main unit file. Your override.conf is just one of those files.
Anatomy of an Override File
The file you edit isn’t a complete unit file. It’s a fragment. You only include the sections and directives you want to change. Let’s say the vendor’s nginx.service has:
[Service]
ExecStart=/usr/sbin/nginx -g 'daemon off;'
…and you want to add a custom configuration file via an environment variable. Your override.conf would look like this:
[Service]
Environment="NGINX_COOL_MODE=1"
# You can add new directives or override existing ones
Restart=always
# This completely replaces the original ExecStart!
ExecStart=
ExecStart=/usr/sbin/nginx -g 'daemon off;' -c /etc/nginx/my-cool-config.conf
Notice the ExecStart= line? This is a systemd quirk you must remember. You can’t just append to an existing ExecStart; you have to clear it first by specifying it with no value, then redefine it. If you forget that empty assignment, systemd will helpfully assume you want to have two ExecStart commands and will fail spectacularly because, well, a service can only have one. It’s a common foot-gun, and I’ve shot myself in the foot with it more times than I’d like to admit.
The Raw, Manual Method
systemctl edit is fantastic, but it’s good to know what’s happening under the hood. The manual process is straightforward:
- Create the drop-in directory:
sudo mkdir -p /etc/systemd/system/nginx.service.d - Create your override file in that directory, e.g.,
sudo vim /etc/systemd/system/nginx.service.d/override.conf - Tell systemd to reload its configuration to pick up the new drop-in:
sudo systemctl daemon-reload
The daemon-reload is crucial. systemd reads unit files once at startup; this command forces it to reread them without a full reboot. Never, ever skip this step after creating or modifying a drop-in manually.
Viewing Your Masterpiece and Debugging
After you’ve made your changes, you naturally want to see the final, assembled product. systemctl cat nginx.service is your go-to command. It will concatenate and print the main unit file and all active drop-ins, in the order they are applied. It’s the absolute best way to debug why your override isn’t behaving as expected.
To see specifically what you’ve overridden, use systemctl show nginx.service. This dumps the entire, parsed, effective configuration as a list of key-value pairs. It’s verbose, but it leaves no room for doubt.
Best Practices and Pitfalls
- Naming Your Drop-Ins: While
override.confis the default, you can use any name ending in.conf. I sometimes use descriptive names like90-custom-port.confto make their purpose obvious, especially if I have multiple drop-ins for a single service. The alphabetical order matters for application! - Don’t Fight the Vendor: Use drop-ins to augment, not gut, the vendor’s unit. If you find yourself needing to override every single line, maybe you should just write your own unit file from scratch and disable the vendor one.
- The
daemon-reloadRitual: I’ll say it again.sudo systemctl daemon-reloadafter any manual change. It’s the equivalent of turning it off and on again, but for systemd’s brain. - Test Before You Restart: Before you
systemctl restart nginx.service, do asystemctl try-reload nginx.serviceif it supports it, or asystemctl restart --dry-run(if available), or at least asystemctl status nginx.serviceto ensure systemd is happy with your new configuration syntax. A failed restart might take your service offline.