Killing a tunnel isn't enough: it comes back in fifteen seconds
While listing what was listening on the server, we found two ports we had never opened. They belonged to a session that had been open for two days and ten hours, from a machine at home. We closed it. Fifteen seconds later, it was back.
1. What we found
The command that lists listening ports returned two unexpected lines. One exposed a service on every interface of the machine; the other made the language model running at home reachable locally. Both belonged to the same process: a remote session left open for two days and ten hours.
This is a reverse tunnel. The principle is simple and often useful: a machine connects to a server and asks, along the way, "open a port on your side, and send me whatever arrives there". People use it to reach a computer behind a home router without configuring anything.
The question is not whether it is useful. It is who decides what listens on a server. Here, the decision had been made by a client machine, one day, for a reason we had long since forgotten.
2. The closing that closed nothing
We killed the session. Both ports vanished, cleanly. Out of caution we looked again fifteen seconds later: both ports were back.
On the client machine, an auto-reconnect utility was watching the tunnel and relaunching it the moment it dropped. That is exactly its job, and it did it well. The consequence, for us, was that no server-side command could durably close that door — because the door was being reopened from outside, in a loop, indefinitely.
It is a textbook case. Killing a process relieves the observer: the line disappears from the screen. But a process you kill that comes back has not been dealt with; it has been disturbed. The only check that would have caught the illusion is the one we ran on reflex — looking a second time, slightly later.
3. The real fix is two lines
The right answer was not to kill harder, nor to block the client machine. It was to remove from the server the capacity to grant that kind of opening at all.
PermitListen none
GatewayPorts no
The first line refuses listen requests issued by a client. The second guarantees that even if a listener slipped through, it would stay confined to the machine and never be exposed to the network. After a reload, the reconnect utility kept connecting — and never obtained a port again.
What we deliberately left alone: local forwarding, the kind development tools use to reach a remote service from a workstation. It makes nobody listen on the server. A guard that banned both would be simpler to write, and would get switched off the first day it got in the way.
4. The rule we take from it
It matches a rule we had already set for applications: nothing runs exposed on the machine, everything lives in a container. The network version of the same idea is this: what listens on a server must have been decided on that server.
Not because a tunnel is dangerous in itself. Because an opening decided elsewhere appears in no configuration, turns up in no file anyone rereads, and outlives the memory of whoever created it. Two days and ten hours is how long it took for this one to surprise us. It could just as easily have lasted six months.
- A killed process that returns has not been dealt with. Looking a second time, slightly later, costs fifteen seconds and changes the conclusion.
- Closing a symptom and closing a door are two different acts. The first shows immediately; only the second holds.
- A guard that bans everything is simpler to write — and it is the one that will get disabled. Refusing precisely costs more and survives.