Kyber Cypher Plain English

Field Logs

Field Log 022

Prove nothing's using it before you shut it off

Field Log // 022 Status Live Difficulty Free Cost Nothing
The Story

I had a couple of databases listening on every network interface instead of just to themselves. That is a real thing worth fixing, and the fix is a one line configuration change. The temptation is to make that change immediately, because it is obviously correct and it takes ten seconds.

Do not make it immediately. Prove nothing is using it first.

A database bound to every interface is reachable from other machines. That is the security problem. It is also, potentially, a feature that something somewhere is quietly depending on. Some job on another box connects to it at three in the morning once a week and has done for a year, and nobody wrote it down, and the person who set it up was you, eighteen months ago.

Security cleanups break things in a particular, nasty way. They break something that runs on a schedule, so the breakage does not appear when you make the change. It appears days later, disconnected from the cause, at which point you have made twelve other changes and the trail is cold.

So I spent twenty minutes proving it instead. I looked at what was actually connected to those ports, from both ends, not just from the machine running the database. Nothing outside the box was talking to them. Then, and only then, I bound them to loopback so they answer only to programs on their own machine, and left everything else exactly as it was.

The same discipline covered the rest of the sweep. I found five services that were running and doing nothing at all, and two databases belonging to a project that was paused rather than abandoned.

The paused project is the interesting case, because it is where people reach for the wrong verb. The instinct when tidying is to remove. But "paused" means someone is coming back to it, and the thing they will need when they come back is the data. So the correct move is to stop the thing and keep everything it owns. The containers stayed. The volumes stayed. Nothing was pruned, nothing was deleted. All that changed is that they are not running and not using memory.

That distinction is worth naming clearly, because the tools blur it. Stopping and deleting are frequently one flag apart, and the flag that also destroys the data is often the one suggested in the first answer you find online. Read the command you are about to run and specifically ask what happens to the volumes.

Here is the frame I keep coming back to. Every cleanup step should be reversible, and you should know the exact command that reverses it before you run the one that does it. Disabling a service is reversible. Deleting its data is not. Closing a port is reversible. Dropping a database is not. When you have a reversible option and an irreversible one that both achieve today's goal, the reversible one is not the cautious choice, it is simply the correct one.

The honest catch: proving something is unused is not the same as proving it will never be used. A snapshot tells you about right now. A weekly job will not appear in a five minute sample, which is exactly why the checking window matters more than the checking command. Watch across a period long enough to contain the schedule you are worried about, and if you genuinely cannot, make the change in a way you can undo in one line and then watch for complaints.

The lesson is one sentence. Prove it is unused, make the change reversible, and never let a tidy up be the thing that destroys data. This is measure before you delete pointed at your network instead of your disk.

The Build

How to see what is actually exposed, prove nothing is using it, close it safely, and stop things without losing their data. Steps one to three are read only and change nothing.

1. See what is actually listening, and on which interface

The important column is the address, not the port. 127.0.0.1 means only this machine can reach it. 0.0.0.0 or :: means anything that can route to this box can reach it. That difference is the whole subject of this log.

ss -ltnp

# just the ones exposed to the network
ss -ltnp | grep -E '0\.0\.0\.0|\[::\]'

2. Prove nothing is connecting, from both ends

Check established connections on the machine that hosts the service. Then go and check from the other side too, because a client that connects on a schedule will show you nothing at all if you only look at the moment it is idle.

# who is connected to this port right now, and from where
ss -tnp state established "( sport = :PORT )"

# from another machine: can it even reach it, and does anything try?
ss -tnp state established "( dport = :PORT )"

Do not skip the remote side. The whole risk here is a dependency you have forgotten, and forgotten dependencies live on other machines by definition.

3. Watch across a real window, not a moment

This is the step people skip and it is the one that catches the weekly job. Sample repeatedly for long enough to cover the schedule you are worried about, and write the results to a file so you are reading data rather than trusting your memory of a terminal.

# log every connection to that port, once a minute, for a day
while true; do
  printf '%s %s\n' "$(date -Is)" \
    "$(ss -tn state established "( sport = :PORT )" | tail -n +2 | wc -l)"
  sleep 60
done >> /tmp/port-watch.log
# then just look. all zeros means nothing used it while you watched.
sort -k2 -n /tmp/port-watch.log | tail -5

4. Bind to loopback instead of every interface

Once you have proof, make the change in the service's own configuration rather than by adding a firewall rule on top. A firewall rule is a second thing that has to keep being true. Binding correctly means the service was never listening in the first place.

# postgres: postgresql.conf
listen_addresses = 'localhost'

# redis: redis.conf
bind 127.0.0.1

# docker: publish to loopback only, not to every interface
ports:
  - "127.0.0.1:5432:5432"      # not "5432:5432"

Then verify with the same command from step one, and confirm the local application that legitimately uses it still works. If machines genuinely do need to reach each other, put them on a private network between themselves rather than opening the port to everything, which is what a private network for all your machines is for.

5. Stop and keep. Do not delete.

For a paused project, the goal is zero resource usage and one hundred percent of the data still on disk. Know which command does which, because they sit uncomfortably close together.

# services: reversible, data untouched
sudo systemctl disable --now some-service
# to bring it back: sudo systemctl enable --now some-service
# containers: stop them by name. keeps containers AND volumes.
docker compose stop

# DANGER: this one deletes the volumes. it is one word longer.
# docker compose down -v

One trap worth knowing: if several unrelated things share a compose project, bringing the project down can take a neighbour with it. Stop services by explicit name rather than taking down the whole project, unless you have checked what else is in there.

6. Write the rollback before you make the change

For each change, write the exact undo command down first, in a file you will still be able to find in three months. If you cannot write the undo, that is the signal to stop and think, not a reason to press on.

# rollback.md, kept next to whatever you changed
# 2026-08-14 bound db to loopback
#   undo: set listen_addresses='*' in postgresql.conf, restart service
# 2026-08-14 stopped paused-project stack
#   undo: docker compose start   (volumes were never removed)

Related: measure before you delete is the same rule for disk space, and every automation gets its own key is the same instinct applied to access.