How to Edit Config Files Safely With sed in Scripts
Edit a config file with sed from a script: set a key whether or not it exists, comment lines, keep a backup, write atomically and validate before reloading.

Provisioning scripts often need one line changed in a file that already exists: turn off password logins, raise a limit, change a port. The line may be commented out, set to another value or missing. A careful edit handles all three, can be run twice, and leaves a way back. This post builds that function, on top of sed with variables. Examples use GNU sed 4.9 and OpenSSH 10 on Debian 13.
Replace or append
set_kv() { # file key value
local file=$1 key=$2 value=$3
if grep -Eq "^[#[:space:]]*${key}[[:space:]]*=" "$file"; then
sed -i.bak -E "s|^[#[:space:]]*(${key}[[:space:]]*=).*|\1 ${value}|" "$file"
else
printf '%s = %s\n' "$key" "$value" >> "$file"
fi
}
set_kv /etc/app.conf max_connections 200The pattern allows a leading # and spaces, so a commented default is uncommented and set in one step. The key is captured and written back with \1, preserving its exact spelling. The grep test decides between replace and append, which makes a second run replace the line with the same text: no change. Keys that contain regular expression characters, such as a dot, should be escaped as shown in the previous post, and the value must not contain the | delimiter or an unescaped &.
Files with a space-separated format
f=/etc/ssh/sshd_config.d/10-local.conf
sudo sed -i.bak -E 's/^#?[[:space:]]*(PasswordAuthentication)[[:space:]].*/\1 no/' "$f"
sudo sshd -t && sudo systemctl reload sshFor key value files such as sshd, the separator is whitespace, so the pattern differs. In sshd, the first value found wins, and a drop-in file in sshd_config.d is a cleaner way than editing the main file; see hardening sshd. Always run sshd -t, which checks syntax, before reloading, and keep an existing session open while testing a fresh login.
Comment out and delete
sed -i.bak -E 's/^([[:space:]]*PermitRootLogin[[:space:]].*)$/# \1/' /etc/ssh/sshd_config
sed -i.bak -E '/^[[:space:]]*Include[[:space:]]/d' app.confCommenting is safer than deleting, since the original line stays as documentation. When a line must be inserted after a section header, use the a command with an address: sed -i '/^\[server\]/a timeout = 30' app.conf. That GNU form needs a different syntax on BSD sed.
Atomic writes and review
tmp=$(mktemp)
sed -E 's/^(port[[:space:]]*=).*/\1 8443/' app.conf > "$tmp"
diff -u app.conf "$tmp" || true
install -m 644 "$tmp" app.confWriting to a temporary file, reviewing the diff and then installing it avoids the window in which sed -i has truncated the file but not finished. install copies with the right mode in one step, though it replaces ownership; add -o and -g if needed. A symbolic link target is replaced by sed -i with a regular file, which is rarely wanted, so edit the real path or use --follow-symlinks (GNU).
Where this does not apply
Structured formats (JSON, YAML, TOML, INI with duplicate sections) are better edited with parsers: jq for JSON, yq for YAML, crudini for INI. Sed edits text, and a regular expression cannot know the nesting. For many servers, a configuration tool is more dependable; see managing config files across servers. For macOS flags, see in-place sed on both systems.
What makes a config edit safe
Editing a configuration file from a script has three failure modes: the edit is wrong, the edit is applied twice, and the edit breaks the file halfway. The first needs a pattern that matches exactly the line you mean and no others, with an anchor at the start and an optional comment character if the key may be commented out. The second needs the edit to be idempotent, so running it again leaves the file as it is: check with grep -q whether the desired line is already present, and change or append only when it is not. The third needs a write that cannot leave a partial file. Write the result to a temporary file in the same directory, check it, copy the ownership and mode from the original, then rename it over the original. A rename within one file system is atomic, so a reader sees the old file or the new one and never half of each.
The in-place option of sed does a version of this by itself. GNU sed writes a temporary file and renames it, which also changes the inode, so a hard link or a bind mounted file stops pointing at the data you edited. Bind mounts of a single file in containers hit this often, and the symptom is that the container never sees the change.
Prefer drop-in files when the program supports them
Many services read a directory of fragments, such as sshd_config.d, sudoers.d, sysctl.d and conf.d for web servers. Writing your setting into a file of your own in that directory avoids editing the vendor's file at all, so a package upgrade cannot conflict with your change and removal is deleting one file. Check how the main file includes the directory, and in what order, since for many programs the first or last value wins and a drop-in named with a high number may be overridden by another. This is the cleanest approach when it is available, and sed is the fallback for files that have no such mechanism. The fallback still deserves care, with a backup copy before the first change and a diff afterwards so you can see exactly what the script did.
Validate before you reload
A syntax error in a configuration file can take a service down at the next reload or, worse, at the next boot when nobody is watching. Most daemons offer a check command: sshd -t, nginx -t, apachectl configtest, visudo -cf file, named-checkconf. Run it on the new file before moving it into place, and abort the edit if it fails. Keep the old file until the service has reloaded successfully and a health check passes, and restore it automatically if not. For remote machines, take extra care with the settings that can lock you out, such as SSH and firewall rules: keep a second session open while you test, or schedule a revert with at that cancels itself when you confirm everything works.
A worked pattern you can adapt
The pattern for a key and value file has four steps. Test whether a line for the key exists, active or commented. If an active line exists, replace its value with an anchored substitution that preserves the key and the spacing around the equals sign. If only a commented line exists, replace it with the active line, or append the new line after it so the history stays visible. If nothing exists, append the line at the end, and for formats with sections, append it inside the right section. Print a diff between the old and new files, and abort if it shows more than the intended single change. A guard such as counting the lines that changed protects against a pattern that matched more than you meant. For INI and YAML, a purpose-built tool such as crudini or yq understands the structure, and is safer than sed for anything with sections or nesting, since a text pattern cannot see the hierarchy.
Keep a backup of the original with a timestamp in the name, outside the configuration directory if the program reads every file in it, since a stray .bak in a directory of fragments can be loaded as configuration.
See the GNU sed manual on in-place editing and sshd_config(5).


