Build1 distinct publisher3 min readPublished
The mechanism is sound, and the adoption bill is only two files plus one line of PAM config. The catch is a script that makes a synchronous call to ifconfig.me on every login, so every successful login now waits on that lookup before any alert goes out.
The Engineer · Build desk

Compiled by The EngineerSomething wrong?How this is made
Read the script in execution order and the cost turns up somewhere the write-up does not flag. It checks PAM_TYPE and returns immediately unless the value is open_session [9]. It sources the config, applies the rate limit, calls hostname, then calls curl against ifconfig.me with a three-second cap to learn its own public address [6]. Only after that does it fork the detached subshell that posts to Telegram under a five-second cap [5]. The post is right that the send never blocks a login, but the address lookup is a separate synchronous call sitting on the same path [15]. On a host with clean egress that costs milliseconds. On a host where outbound 443 is filtered or DNS is limping, every successful login waits up to three seconds before the message is even queued, and scripted rsync sessions wait with it [15]. It asks a website on the internet what its own IP address is, once per login [6].
The exit-0 rule is the part I would copy verbatim. The stated justification is that a non-zero exit from a pam_exec hook can be configured to block the login it was installed to report [4]. The words doing the work there are "can be configured." Whether a broken script locks you out of your own server depends on the control flag on the pam_exec line in /etc/pam.d/sshd, and that line is the one piece of the build the walkthrough renders as a diagram rather than printing as a listing [3]. So the discipline is right for the author's reason, and also for one he does not give: a reader cannot size the blast radius of the hook from the article alone.
The rate limiter is careful work. Keyed on user plus source host with a sixty-second window [7], it turns the three or four sessions that VS Code Remote SSH or rsync open per connection into a single message, suppressing three of every four [8][16]. It also drops a timestamp file per distinct user and source address into /var/lib/ssh-notify [7]. That directory grows, but only on success, because open_session never fires for an attempt that failed [1]. The brute-force traffic that starts arriving within minutes of a fresh VPS going public [13] costs this rig nothing, since none of it reaches the hook [17]. That is the correct coverage choice. It also means the tool answers exactly one question, which is whether anybody got in, and reports who and from where with a UTC timestamp [14].
For the behaviour to transfer to your fleet, two things have to hold: the host can reach api.telegram.org at the moment of login, and somebody is watching for expected traffic that stops. The script fails silently under all circumstances by design [4], and the listing carries no retry and no queue [5], so a dropped send looks exactly like a quiet night. Treat it as a doorbell. In my context that is a fair trade on a box I already watch other ways, and the whole adoption bill is two files and one PAM line [11][12][2]. If it is the only thing watching the box, the silence is the part to engineer around.
Ranked by verification strength, evidence, and original report placement.
SSH login events pass through PAM on Linux, and the pam_exec module can run an arbitrary script at specific points in the auth lifecycle, including open_session, which fires only after a successful login.
The described setup is about 30 lines of Bash plus one line of PAM config, with no agent, no daemon and no third-party service beyond Telegram's free Bot API.
The post presents the architecture as a diagram: /etc/pam.d/sshd running pam_exec on open_session, which runs /usr/local/bin/ssh-notify, which curls the Telegram Bot API, with no polling, no log-tailing and no extra background processes.
The script is written to exit 0 under all circumstances because, the post says, a non-zero exit code from a pam_exec hook can be configured to block the login it is supposed to be reporting on; it fails silently, always.
The Telegram POST runs inside a detached subshell (setsid curl ... &) with curl --max-time 5, so a slow API or network hiccup never delays the SSH login; the listing contains no retry or queue.
Before building the message the script runs host=$(hostname) and ip=$(curl -sf --max-time 3 https://ifconfig.me 2>/dev/null || echo "unknown"), inline and ahead of the detached send.
Distinct publishers with included, body-backed reporting in this cluster.
dev.to
1 article · September 2, 2026
Follow any of these and your For You feed starts watching them — no settings page required.
build
The load average had already peaked: reading 11.08 / 38.69 / 23.59 in the right order1 distinct publisher
build
Three manual interventions in a month, and every guard was working as designed1 distinct publisher
build
Six MariaDB versions, one real difference: the only reason to leave 10.6 is the July 2026 clock1 distinct publisher
build
Force the tool call, then hand Lightsail a long-lived key1 distinct publisher
Evidence-backed comparisons of source perspectives and observed adoption signals. Read the methodology
Which Builder, Operator, and Investor concerns the observed source mix emphasized—not a truth score.
Evidence, demonstrated adoption, hype gap, incentives, and confidence are assessed independently, each on its own current evidence. How these are measured.
One post, but the code is on the page
Everything traces to a single self-published how-to on dev.to with no second author and no independent test. What lifts it above the usual single-source floor is that the central artifact is the complete script: the PAM gate, the 60-second window, the timeouts and the ordering of the two curls are all inspectable rather than asserted. The assertions that are not inspectable — brute-force traffic 'within minutes', VS Code Remote firing three to four times — carry no data at all.
No deployment signal
Nothing in this reporting says anyone is running it. There is no repository, no install count, no reader reporting back, not even a claim about the author's own fleet — a tutorial published in September and an invitation to leave a comment is the whole record.
Overstated by one inline curl
The mechanism claim is clean — pam_exec on open_session really is this small, and the detached subshell really does keep Telegram off the critical path. The overreach is narrow and specific: 'your SSH login is never delayed waiting on it' is written directly above a foreground request to ifconfig.me with a three-second timeout. Add the unmeasured 3-4 notification anecdote used to justify the rate limiter and the framing sits modestly ahead of what the code and evidence support.
Disclosed funnel for a paid tool
The author states the arrangement himself: this snippet is the free signature feature of his ServerSecure Setup product, given away because 'it costs me nothing' and because it is the best proof the rest is built with the same care. Disclosure is worth credit, and it does not change the pull — the piece is written to make one module look production-grade, which is exactly the register in which an inline three-second network call goes unmentioned.
Verifiable mechanics, unverified in the field
We are confident about what the script does, because it is printed and PAM's open_session semantics are well understood. We are not confident about how it behaves on a real box: no one has timed the ifconfig.me hop on a loaded server, counted the notifications VS Code Remote actually triggers, or reported a delivery failure. Read it as a design you can audit, not a result anyone has reproduced.