<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: ogurexxx</title>
    <description>The latest articles on DEV Community by ogurexxx (@ogurexxx).</description>
    <link>https://dev.to/ogurexxx</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4239503%2Fd8b6622f-c4ed-4db0-83d4-02dc56c8cc48.png</url>
      <title>DEV Community: ogurexxx</title>
      <link>https://dev.to/ogurexxx</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ogurexxx"/>
    <language>en</language>
    <item>
      <title>Five outages in three days: finding where my server's connection breaks</title>
      <dc:creator>ogurexxx</dc:creator>
      <pubDate>Wed, 11 Nov 2026 16:48:45 +0000</pubDate>
      <link>https://dev.to/ogurexxx/five-outages-in-three-days-finding-where-my-servers-connection-breaks-25d4</link>
      <guid>https://dev.to/ogurexxx/five-outages-in-three-days-finding-where-my-servers-connection-breaks-25d4</guid>
      <description>&lt;p&gt;In three days the site dropped off the network five times, the longest for five hours. The server itself ran without a single error the whole time: no reboots, no crashes, stats kept coming in. Only the road from it to the internet kept disappearing. Here is how I tracked that down and what I changed so that next time the answer is ready on its own.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;A small server that runs my portfolio site and an SSH honeypot for bots. The site lives in Docker behind a reverse proxy; next to it a watchdog made of bash and systemd timers checks every ten minutes that everything works and sends a push notification if something breaks. An external monitor checks the site from outside every five minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  "0 problems" while the site is down
&lt;/h2&gt;

&lt;p&gt;I was not the first to learn about the outages: an external monitor was. It emails me when the site stops answering. At the same time the server's own check was writing "0 problems" to its log every ten minutes.&lt;/p&gt;

&lt;p&gt;It was not wrong. It checks what is inside: whether the data is fresh, containers and services are running, there is disk space left. Inside, everything was fine. That is the first lesson of this story: a check from inside sees the server, not the path to it. A visitor does not care that the processes are alive if nobody can reach them.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the logs showed
&lt;/h2&gt;

&lt;p&gt;First I ruled out the obvious: the server had not rebooted in six days, no container had restarted, the kernel log had no out-of-memory or disk errors. Docker, however, kept logging the same error at the right minutes: it could not ask DNS for the address of anything, from the certificate authority to my own domains. Timeout, timeout, timeout.&lt;/p&gt;

&lt;p&gt;To see where the outages begin and end, it is enough to bucket these errors into ten-minute slots:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;journalctl -u docker --since today -o short-iso --no-pager &lt;span class="o"&gt;|&lt;/span&gt; grep &lt;span class="s1"&gt;'failed to query external DNS'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; cut -c1-15 &lt;span class="o"&gt;|&lt;/span&gt; uniq -c
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The picture came out neat: silence, a half-hour burst, silence again, then an hour and a half of solid errors. The bursts matched the monitor's emails to the minute. The server could neither reach outside nor accept connections from outside, so the whole link was gone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Teaching the watchdog to notice a drop
&lt;/h2&gt;

&lt;p&gt;The first fix is obvious: also check the outside connection. Now the watchdog asks DNS and tries to connect to a couple of public addresses every ten minutes.&lt;/p&gt;

&lt;p&gt;The tricky part is elsewhere: while the link is down there is no way to report it, a push simply will not leave the server. So the watchdog remembers when the first check failed, and when the link comes back it sends one message, "offline from … to …", and prepares a draft write-up by itself. The day turns red on the uptime bar right away, without waiting for me to write the text.&lt;/p&gt;

&lt;h2&gt;
  
  
  A false alarm
&lt;/h2&gt;

&lt;p&gt;Two days later the watchdog caught three drops. I was about to write them up, but first decided to check whether visitors had actually noticed. The best witness here is the site's own access log: if external requests kept arriving, the site was reachable, whatever the watchdog thought.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 -c &lt;span class="s1"&gt;'
import sys, json, datetime, ipaddress, collections
c = collections.Counter()
for line in sys.stdin:
    e = json.loads(line)
    if ipaddress.ip_address(e["request"]["remote_ip"]).is_global:
        c[datetime.datetime.fromtimestamp(e["ts"]).strftime("%H:%M")[:4] + "0"] += 1
print(sorted(c.items()))'&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; access.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script only counts requests from public addresses: internal ones, such as checks from the local network, do not count.&lt;/p&gt;

&lt;p&gt;Two drops were confirmed: not a single request came in from outside during those hours. The first one, ten minutes long, turned out to be false. Only DNS was failing, and visitors were getting in just fine. The watchdog saw that the server could not reach outside and concluded that the site was down.&lt;/p&gt;

&lt;p&gt;That is the second lesson: "the server cannot reach outside" and "the site is down" are different events, and both need checking. Now, before writing a draft, the watchdog looks at the access log itself. If requests came in during at least half of the gap, it only sends a push saying the site was reachable all along, and the uptime bar stays green.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where exactly it breaks
&lt;/h2&gt;

&lt;p&gt;What was left was to find out whose side the drop is on. That takes two addresses: the gateway the server uses to reach the network, and the first provider hop behind it. The gateway comes from the routing table, and the first provider hop can be found with a simple trick: send a ping with a time-to-live of 2.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ping -n -c1 -t 2 1.1.1.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The packet dies at the second hop, and that hop answers "Time to live exceeded", giving away its address. While the link is up, the watchdog remembers both addresses once a day. During an outage it pings them every ten minutes and writes a verdict into the draft:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the gateway did not answer: the fault is on our side;&lt;/li&gt;
&lt;li&gt;the gateway answered but the provider hop did not: the line to the provider failed;&lt;/li&gt;
&lt;li&gt;both answered: the problem is further out in the provider's network, or DNS only.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With a verdict like that you can go to support not with "it sometimes doesn't work" but with exact times and a location.&lt;/p&gt;

&lt;h2&gt;
  
  
  An outside view that works during an outage
&lt;/h2&gt;

&lt;p&gt;The final touch: a status page that opens even when the site is down. It lives on GitHub Pages, checks the site every five minutes from GitHub Actions and sits on the subdomain &lt;code&gt;status.ogurexxx.ru&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;There was a trap here too. The site has HSTS enabled for all subdomains: the browser opens them only over HTTPS with a valid certificate and does not let you click through a warning. Until the certificate for the new subdomain is issued, the page simply does not open, even if DNS is set up correctly. And some monitoring services only issue a certificate for your own domain on a paid plan.&lt;/p&gt;

&lt;h2&gt;
  
  
  How much downtime can I afford
&lt;/h2&gt;

&lt;p&gt;After this story the incidents page got a reliability table: number of outages, total downtime, mean time to recover (MTTR) and mean time between failures (MTBF). And an error budget, SRE-style. The target is 99.5% availability per month, which is about 3 hours 40 minutes of downtime.&lt;/p&gt;

&lt;p&gt;November burned through that budget in its first week: the two outages on 5 November alone took more than five hours. It is an unpleasant number, but a useful one. While budget is left, I can experiment freely. Once it is gone, there is one priority, finding the cause of the drops, and new features wait.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I took away
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;A check from inside and a check from outside answer different questions. You need both.&lt;/li&gt;
&lt;li&gt;During an outage the server cannot report it by itself. So the report has to arrive after recovery, with the facts: how long it lasted and where it broke.&lt;/li&gt;
&lt;li&gt;Before writing a write-up, ask a witness the outage did not touch. The access log honestly showed which drop was real.&lt;/li&gt;
&lt;li&gt;Five drops in three days is no longer chance but a fault. Now I have the data to find it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Write-ups of each outage are on the &lt;a href="https://ogurexxx.ru/incidents?lang=en" rel="noopener noreferrer"&gt;Incidents&lt;/a&gt; page, the current state is at &lt;a href="https://status.ogurexxx.ru" rel="noopener noreferrer"&gt;status.ogurexxx.ru&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This post was first published in Russian on &lt;a href="https://habr.com/ru/articles/1106762/" rel="noopener noreferrer"&gt;Habr&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>sre</category>
      <category>monitoring</category>
      <category>linux</category>
    </item>
    <item>
      <title>An SSH honeypot on my own server: 24,000 sessions in five days and one very stubborn bot</title>
      <dc:creator>ogurexxx</dc:creator>
      <pubDate>Wed, 11 Nov 2026 16:39:56 +0000</pubDate>
      <link>https://dev.to/ogurexxx/an-ssh-honeypot-on-my-own-server-24000-sessions-in-five-days-and-one-very-stubborn-bot-174n</link>
      <guid>https://dev.to/ogurexxx/an-ssh-honeypot-on-my-own-server-24000-sessions-in-five-days-and-one-very-stubborn-bot-174n</guid>
      <description>&lt;p&gt;I set up an SSH honeypot on my server: it pretends to be a defenceless Linux box, accepts almost any password and records everything done inside. There is no real system behind it. In the first five days it received 24,218 sessions from 683 addresses, and almost nine in ten came from a single bot.&lt;/p&gt;

&lt;p&gt;I wanted to see attacks on my own server rather than in reports and textbooks: who shows up, what they type, and how one attack differs from another. Other companies' statistics answer "how many"; a honeypot shows "how".&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;The honeypot lives next to my portfolio site. It is &lt;a href="https://github.com/cowrie/cowrie" rel="noopener noreferrer"&gt;Cowrie&lt;/a&gt; in an unprivileged, read-only container with every outbound connection blocked: whatever an attacker types, nothing leaves the box. Next to it run a tarpit, a port that pretends to be SSH and never finishes its greeting, and Redis, MySQL and Elasticsearch decoys. Incoming web traffic is filtered by CrowdSec.&lt;/p&gt;

&lt;p&gt;Every five minutes the logs are turned into summaries: commands are cleaned (links, addresses and random file names become placeholders such as &lt;code&gt;&amp;lt;url&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;file&amp;gt;&lt;/code&gt;), and attacker addresses are never published. Everything below is visible live on the site.&lt;/p&gt;

&lt;h2&gt;
  
  
  Found quickly, and the obvious gets tried first
&lt;/h2&gt;

&lt;p&gt;A new session typically arrives about five seconds after the last one. The server was never advertised: it is found by scanners that constantly sweep the whole internet.&lt;/p&gt;

&lt;p&gt;The most common passwords are predictable: &lt;code&gt;123456&lt;/code&gt;, &lt;code&gt;123&lt;/code&gt;, &lt;code&gt;1234&lt;/code&gt;, &lt;code&gt;12345678&lt;/code&gt;. Besides &lt;code&gt;root&lt;/code&gt;, the usernames include &lt;code&gt;ubuntu&lt;/code&gt;, &lt;code&gt;admin&lt;/code&gt;, &lt;code&gt;deploy&lt;/code&gt;, &lt;code&gt;postgres&lt;/code&gt; and also &lt;code&gt;claude&lt;/code&gt;: bots are already looking for accounts of developers who use AI tools. The conclusion is simple: password login on a public server does not last long.&lt;/p&gt;

&lt;h2&gt;
  
  
  One bot made almost 90% of the noise
&lt;/h2&gt;

&lt;p&gt;The most interesting part was the distribution. 21,217 sessions, or 88%, came from a single family using 18 addresses. It logs in and runs one command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uname -s -v -n -r -m
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then it leaves. This does not look like a breach so much as an inventory: the bot checks whether the password worked and what system is inside. I cannot prove it, but the behaviour fits collecting a list of accessible servers to return to later or hand to someone else.&lt;/p&gt;

&lt;p&gt;That gave the first practical lesson: counting sessions is useless. One noisy bot drowns out everything else, so I rebuilt the experiments to count addresses instead of sessions.&lt;/p&gt;

&lt;h2&gt;
  
  
  The most widespread family leaves a key
&lt;/h2&gt;

&lt;p&gt;The family with the most addresses, 291, does something different: it removes the protection from the &lt;code&gt;.ssh&lt;/code&gt; folder, deletes it, recreates it and writes its own key. The whole thing takes seconds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ~ &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; rm -rf .ssh &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; mkdir .ssh &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;key&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; .ssh/authorized_keys
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Deleting the old folder matters: on a real server it would lock out both the owner and other bots. And changing the password afterwards does not help, because the key stays. So when you suspect a breach, check not only passwords but also the &lt;code&gt;authorized_keys&lt;/code&gt; file, cron jobs and the user list.&lt;/p&gt;

&lt;h2&gt;
  
  
  What bots left in the honeypot
&lt;/h2&gt;

&lt;p&gt;The honeypot may not make outbound connections, so it cannot download anything. But bots upload files themselves: 22 files have piled up so far, 17 of them Linux programs for several architectures, including x86-64, ARM and AArch64. Several carry signs of XMRig, a cryptocurrency miner. The files are only examined as data and never run. The goal of most attacks is boring: someone else's CPU.&lt;/p&gt;

&lt;h2&gt;
  
  
  The tarpit: how much time you can steal from bots
&lt;/h2&gt;

&lt;p&gt;The tarpit follows RFC 4253: before the banner a server may send any lines it likes, and the client has to wait. It sends one random line every ten seconds and holds the connection for up to a day. All that time the bot attacks nobody.&lt;/p&gt;

&lt;p&gt;A week after launch the counter showed over 2,000 hours stolen from attackers and over 3,400 connections caught. The record is 24 hours, because that is as long as the tarpit holds anyone. If each of those bots had tried one password per second, that would be about 7.5 million password guesses that never happened. At any moment a couple of dozen bots are stuck in it.&lt;/p&gt;

&lt;h2&gt;
  
  
  How half my defences stayed silent for two days
&lt;/h2&gt;

&lt;p&gt;The most useful part turned out not to be about bots at all. For two days the tarpit and decoy counters sat at zero. The service was running, the firewall rule was in place, and its counter was even growing. A local check passed.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;tcpdump&lt;/code&gt; helped: requests were arriving, but no replies went out. In the end a second firewall turned up. Permission in one does not override a block in the other: a packet has to pass both. The first firewall's counter said all was well, and that was only half true.&lt;/p&gt;

&lt;p&gt;The conclusion: a check from inside does not prove a service is reachable from outside. A firewall counter does not prove it either: it only speaks for its own part of the path. The most reliable answer came from a simple experiment: a real connection from outside while watching the packets.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;The next question is whether bots pay attention to what they are dealing with. Every day the honeypot changes its disguise: the server name and the system version. In a few weeks it will be clear whether bots react or do not care. If they do not care, that is a result too.&lt;/p&gt;

&lt;p&gt;The honeypot and the site also carry bait keys: a fake &lt;code&gt;.env&lt;/code&gt; with an API key unique to everyone who downloads it. If someone tries the key, I will see how many minutes passed since it was stolen and whether it came from the same address. That gets its own post once there is data.&lt;/p&gt;

&lt;p&gt;Live honeypot data is on the &lt;a href="https://ogurexxx.ru/live?lang=en" rel="noopener noreferrer"&gt;live page&lt;/a&gt;, every botnet family with its lineage is in the &lt;a href="https://ogurexxx.ru/bestiary?lang=en" rel="noopener noreferrer"&gt;bestiary&lt;/a&gt;, and you can check your own &lt;code&gt;auth.log&lt;/code&gt; against these bots &lt;a href="https://ogurexxx.ru/live/logcheck?lang=en" rel="noopener noreferrer"&gt;right in the browser&lt;/a&gt;: the log never leaves your machine.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This post was first published in Russian on &lt;a href="https://habr.com/ru/articles/1106744/" rel="noopener noreferrer"&gt;Habr&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>linux</category>
      <category>honeypot</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
