<?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: Anguishe</title>
    <description>The latest articles on DEV Community by Anguishe (@bashsnippets).</description>
    <link>https://dev.to/bashsnippets</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3909567%2F885dee1e-f72c-48d7-965f-91ee8ade012a.jpeg</url>
      <title>DEV Community: Anguishe</title>
      <link>https://dev.to/bashsnippets</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/bashsnippets"/>
    <language>en</language>
    <item>
      <title>Your Server Is at 97% CPU Right Now. Would You Know?</title>
      <dc:creator>Anguishe</dc:creator>
      <pubDate>Fri, 22 May 2026 11:00:00 +0000</pubDate>
      <link>https://dev.to/bashsnippets/your-server-is-at-97-cpu-right-now-would-you-know-582c</link>
      <guid>https://dev.to/bashsnippets/your-server-is-at-97-cpu-right-now-would-you-know-582c</guid>
      <description>&lt;p&gt;Here's how it usually goes:&lt;/p&gt;

&lt;p&gt;You deploy something. Traffic is light. Server load sits at 15% and you move on to the next thing. Then traffic grows, or a cron job stacks on itself, or a memory leak slowly eats through your RAM over 72 hours. By the time you notice, the server is thrashing, responses take 8 seconds, and your app is effectively dead.&lt;/p&gt;

&lt;p&gt;The frustrating part is that the tools to catch this have been on your server the entire time. &lt;code&gt;top&lt;/code&gt; and &lt;code&gt;free&lt;/code&gt; ship with every Linux distribution ever made. Nobody installs them. They're just... there. Waiting for someone to actually ask.&lt;/p&gt;

&lt;p&gt;So I wrote a script that asks every hour and logs a warning when the answer is bad.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Script
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="nv"&gt;CHECK&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"✓"&lt;/span&gt;
&lt;span class="nv"&gt;CROSS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"✗"&lt;/span&gt;

&lt;span class="c"&gt;# --- Configuration ---&lt;/span&gt;
&lt;span class="nv"&gt;THRESHOLD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;80                    &lt;span class="c"&gt;# Alert when usage exceeds this %&lt;/span&gt;
&lt;span class="nv"&gt;LOG_FILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/var/log/resource-monitor.log"&lt;/span&gt;
&lt;span class="nv"&gt;DATE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="s1"&gt;'+%Y-%m-%d %H:%M:%S'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# --- CPU Usage ---&lt;/span&gt;
&lt;span class="nv"&gt;CPU&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;top &lt;span class="nt"&gt;-bn1&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"Cpu(s)"&lt;/span&gt; | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{print $2}'&lt;/span&gt; | &lt;span class="nb"&gt;cut&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;&lt;span class="s1"&gt;'%'&lt;/span&gt; &lt;span class="nt"&gt;-f1&lt;/span&gt; | &lt;span class="nb"&gt;cut&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;&lt;span class="s1"&gt;','&lt;/span&gt; &lt;span class="nt"&gt;-f1&lt;/span&gt; | xargs &lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s2"&gt;"%.0f"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# --- RAM Usage ---&lt;/span&gt;
&lt;span class="nv"&gt;RAM&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;free | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'/Mem:/ {printf "%.0f", $3/$2*100}'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"[&lt;/span&gt;&lt;span class="nv"&gt;$DATE&lt;/span&gt;&lt;span class="s2"&gt;] CPU: &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CPU&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;% | RAM: &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;RAM&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;%"&lt;/span&gt;

&lt;span class="c"&gt;# --- CPU Alert ---&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CPU&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-gt&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$THRESHOLD&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CROSS&lt;/span&gt;&lt;span class="s2"&gt; [&lt;/span&gt;&lt;span class="nv"&gt;$DATE&lt;/span&gt;&lt;span class="s2"&gt;] WARNING: CPU at &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CPU&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;% (threshold: &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;THRESHOLD&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;%)"&lt;/span&gt; | &lt;span class="nb"&gt;tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOG_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;else
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CHECK&lt;/span&gt;&lt;span class="s2"&gt; CPU OK: &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CPU&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;%"&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="c"&gt;# --- RAM Alert ---&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RAM&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-gt&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$THRESHOLD&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CROSS&lt;/span&gt;&lt;span class="s2"&gt; [&lt;/span&gt;&lt;span class="nv"&gt;$DATE&lt;/span&gt;&lt;span class="s2"&gt;] WARNING: RAM at &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;RAM&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;% (threshold: &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;THRESHOLD&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;%)"&lt;/span&gt; | &lt;span class="nb"&gt;tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOG_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;else
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CHECK&lt;/span&gt;&lt;span class="s2"&gt; RAM OK: &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;RAM&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;%"&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Runs in under a second. Zero dependencies. Works on Ubuntu, Debian, CentOS, RHEL, Arch — anything with &lt;code&gt;top&lt;/code&gt; and &lt;code&gt;free&lt;/code&gt;, which is everything.&lt;/p&gt;




&lt;h2&gt;
  
  
  How the CPU Check Actually Works
&lt;/h2&gt;

&lt;p&gt;The CPU line looks intimidating, so let me walk through it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;top &lt;span class="nt"&gt;-bn1&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"Cpu(s)"&lt;/span&gt; | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{print $2}'&lt;/span&gt; | &lt;span class="nb"&gt;cut&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;&lt;span class="s1"&gt;'%'&lt;/span&gt; &lt;span class="nt"&gt;-f1&lt;/span&gt; | &lt;span class="nb"&gt;cut&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;&lt;span class="s1"&gt;','&lt;/span&gt; &lt;span class="nt"&gt;-f1&lt;/span&gt; | xargs &lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s2"&gt;"%.0f"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;top -bn1&lt;/code&gt;&lt;/strong&gt; — runs &lt;code&gt;top&lt;/code&gt; in batch mode (&lt;code&gt;-b&lt;/code&gt;) for exactly one iteration (&lt;code&gt;-n1&lt;/code&gt;). Batch mode dumps the full output to stdout instead of opening the interactive TUI. This is the only way to use &lt;code&gt;top&lt;/code&gt; in a script.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;grep "Cpu(s)"&lt;/code&gt;&lt;/strong&gt; — grabs the line that shows aggregate CPU stats.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;awk '{print $2}'&lt;/code&gt;&lt;/strong&gt; — pulls the user CPU percentage (the second field).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;cut&lt;/code&gt; and &lt;code&gt;xargs printf&lt;/code&gt;&lt;/strong&gt; — strips the percent sign and any comma decimal separator, then rounds to an integer. You can't do integer comparison in bash with &lt;code&gt;2.5&lt;/code&gt; — it needs a clean number like &lt;code&gt;3&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The RAM check is simpler: &lt;code&gt;free&lt;/code&gt; shows total and used memory, and &lt;code&gt;awk&lt;/code&gt; divides used by total and multiplies by 100.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Thing About &lt;code&gt;tee -a&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;You'll notice the script uses &lt;code&gt;echo ... | tee -a "$LOG_FILE"&lt;/code&gt; for warnings but plain &lt;code&gt;echo&lt;/code&gt; for healthy checks. This is intentional.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;tee -a&lt;/code&gt; writes to the terminal AND appends to the log file simultaneously. When everything is fine, there's nothing to log — you don't want a log file full of "CPU OK" lines every hour for three years. You only want entries when something is actually wrong. So the log file becomes a clean history of every resource spike your server has had, with timestamps.&lt;/p&gt;

&lt;p&gt;When something breaks at 2 AM and you're debugging at 9 AM, you can &lt;code&gt;cat /var/log/resource-monitor.log&lt;/code&gt; and see exactly when resources started climbing.&lt;/p&gt;




&lt;h2&gt;
  
  
  Schedule It
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;crontab &lt;span class="nt"&gt;-e&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hourly checks (what I use for most servers):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;0 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; /home/user/monitor.sh &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /var/log/monitor-cron.log 2&amp;gt;&amp;amp;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every 5 minutes (for production servers where you need tighter visibility):&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="k"&gt;*&lt;/span&gt;/5 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; /home/user/monitor.sh &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /var/log/monitor-cron.log 2&amp;gt;&amp;amp;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not sure about the cron syntax? I have a &lt;a href="https://bashsnippets.xyz/tools/cron-job-builder.html" rel="noopener noreferrer"&gt;cron job builder tool&lt;/a&gt; that generates the line visually.&lt;/p&gt;




&lt;h2&gt;
  
  
  Variations That Are Worth Adding
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Add an email alert when thresholds are breached:&lt;/strong&gt;&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="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CPU&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-gt&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$THRESHOLD&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nv"&gt;MSG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CROSS&lt;/span&gt;&lt;span class="s2"&gt; WARNING: CPU at &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CPU&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;% on &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;hostname&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; at &lt;/span&gt;&lt;span class="nv"&gt;$DATE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$MSG&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOG_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$MSG&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | mail &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"[ALERT] High CPU on &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;hostname&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; you@example.com
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I have a full &lt;a href="https://bashsnippets.xyz/snippets/bash-send-email-alert.html" rel="noopener noreferrer"&gt;email alert script&lt;/a&gt; that covers the &lt;code&gt;mail&lt;/code&gt; setup if you haven't configured it before.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check disk space in the same script:&lt;/strong&gt;&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="nv"&gt;DISK&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;df&lt;/span&gt; / | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'NR==2 {print $5}'&lt;/span&gt; | &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'%'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DISK&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-gt&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$THRESHOLD&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CROSS&lt;/span&gt;&lt;span class="s2"&gt; [&lt;/span&gt;&lt;span class="nv"&gt;$DATE&lt;/span&gt;&lt;span class="s2"&gt;] WARNING: Disk at &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DISK&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;%"&lt;/span&gt; | &lt;span class="nb"&gt;tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOG_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you've got CPU, RAM, and disk in one pass. I keep disk in a &lt;a href="https://bashsnippets.xyz/snippets/disk-space-warning.html" rel="noopener noreferrer"&gt;separate script&lt;/a&gt; because I use a different threshold for it (90% vs 80%), but combining them works fine if you want fewer cron entries.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Log to a CSV for trending:&lt;/strong&gt;&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;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DATE&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="nv"&gt;$CPU&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="nv"&gt;$RAM&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /var/log/resource-history.csv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run this for a week and you'll see patterns. Maybe your app spikes every day at 2 PM when a batch job runs. Maybe RAM creeps up 1% per day, which means you have a memory leak that'll hit the wall in three months. You can't see these patterns without historical data.&lt;/p&gt;




&lt;h2&gt;
  
  
  When This Isn't Enough
&lt;/h2&gt;

&lt;p&gt;This script is a notification system, not a monitoring platform. It tells you "something is wrong right now" but doesn't give you graphs, dashboards, or historical trending out of the box.&lt;/p&gt;

&lt;p&gt;If you need that level of visibility, tools like Netdata (free, runs locally) or Grafana + Prometheus are the next step. But for a single VPS or a handful of servers, a cron script that logs warnings and optionally emails you is 90% of what you need — and it takes 2 minutes to deploy instead of 2 hours.&lt;/p&gt;




&lt;p&gt;Full script, line-by-line breakdown, cron setup, and more variations:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://bashsnippets.xyz/snippets/monitor-cpu-ram-usage.html" rel="noopener noreferrer"&gt;bashsnippets.xyz/snippets/monitor-cpu-ram-usage.html&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>bash</category>
      <category>linux</category>
      <category>sysadmin</category>
      <category>monitoring</category>
    </item>
    <item>
      <title>My Nginx Died at 2 AM and Nobody Noticed for 6 Hours. Now I Have a Watchdog Script.</title>
      <dc:creator>Anguishe</dc:creator>
      <pubDate>Thu, 21 May 2026 11:00:00 +0000</pubDate>
      <link>https://dev.to/bashsnippets/my-nginx-died-at-2-am-and-nobody-noticed-for-6-hours-now-i-have-a-watchdog-script-3lnj</link>
      <guid>https://dev.to/bashsnippets/my-nginx-died-at-2-am-and-nobody-noticed-for-6-hours-now-i-have-a-watchdog-script-3lnj</guid>
      <description>&lt;p&gt;Nginx crashed on a Saturday night. An OOM kill, probably — I was running a Node app that leaked memory like a broken faucet. The service went down at 2:14 AM. I found out at 8:30 AM when I opened my laptop and saw Slack messages from six hours earlier asking why the site was down.&lt;/p&gt;

&lt;p&gt;The fix took 10 seconds: &lt;code&gt;sudo systemctl start nginx&lt;/code&gt;. The downtime cost me a weekend of credibility.&lt;/p&gt;

&lt;p&gt;The thing is, &lt;code&gt;systemctl&lt;/code&gt; already knows when a service dies. I just wasn't asking it to check. So I wrote a script that asks every 60 seconds and restarts the service if it's down. Took less time to write than it did to explain the outage to my team.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Script
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="nv"&gt;CHECK&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"✓"&lt;/span&gt;
&lt;span class="nv"&gt;CROSS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"✗"&lt;/span&gt;

&lt;span class="c"&gt;# --- Configuration ---&lt;/span&gt;
&lt;span class="nv"&gt;SERVICE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"nginx"&lt;/span&gt;                              &lt;span class="c"&gt;# Change to your service name&lt;/span&gt;
&lt;span class="nv"&gt;LOG_FILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/var/log/service-watchdog.log"&lt;/span&gt;
&lt;span class="nv"&gt;DATE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="s1"&gt;'+%Y-%m-%d %H:%M:%S'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;NOTIFY_EMAIL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;                              &lt;span class="c"&gt;# Optional: you@example.com&lt;/span&gt;

&lt;span class="c"&gt;# --- Check if service is running ---&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;systemctl is-active &lt;span class="nt"&gt;--quiet&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SERVICE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CHECK&lt;/span&gt;&lt;span class="s2"&gt; [&lt;/span&gt;&lt;span class="nv"&gt;$DATE&lt;/span&gt;&lt;span class="s2"&gt;] &lt;/span&gt;&lt;span class="nv"&gt;$SERVICE&lt;/span&gt;&lt;span class="s2"&gt; is running"&lt;/span&gt;
&lt;span class="k"&gt;else
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CROSS&lt;/span&gt;&lt;span class="s2"&gt; [&lt;/span&gt;&lt;span class="nv"&gt;$DATE&lt;/span&gt;&lt;span class="s2"&gt;] &lt;/span&gt;&lt;span class="nv"&gt;$SERVICE&lt;/span&gt;&lt;span class="s2"&gt; is NOT running — attempting restart..."&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CROSS&lt;/span&gt;&lt;span class="s2"&gt; [&lt;/span&gt;&lt;span class="nv"&gt;$DATE&lt;/span&gt;&lt;span class="s2"&gt;] &lt;/span&gt;&lt;span class="nv"&gt;$SERVICE&lt;/span&gt;&lt;span class="s2"&gt; DOWN — restarting"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOG_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

  &lt;span class="c"&gt;# --- Attempt restart ---&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl start &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SERVICE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CHECK&lt;/span&gt;&lt;span class="s2"&gt; [&lt;/span&gt;&lt;span class="nv"&gt;$DATE&lt;/span&gt;&lt;span class="s2"&gt;] &lt;/span&gt;&lt;span class="nv"&gt;$SERVICE&lt;/span&gt;&lt;span class="s2"&gt; restarted successfully"&lt;/span&gt; | &lt;span class="nb"&gt;tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOG_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

    &lt;span class="c"&gt;# --- Optional: send email notification ---&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$NOTIFY_EMAIL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
      &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SERVICE&lt;/span&gt;&lt;span class="s2"&gt; was down and has been restarted on &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;hostname&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; at &lt;/span&gt;&lt;span class="nv"&gt;$DATE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
        | mail &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"[RECOVERED] &lt;/span&gt;&lt;span class="nv"&gt;$SERVICE&lt;/span&gt;&lt;span class="s2"&gt; restarted"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$NOTIFY_EMAIL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;fi
  else
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CROSS&lt;/span&gt;&lt;span class="s2"&gt; [&lt;/span&gt;&lt;span class="nv"&gt;$DATE&lt;/span&gt;&lt;span class="s2"&gt;] &lt;/span&gt;&lt;span class="nv"&gt;$SERVICE&lt;/span&gt;&lt;span class="s2"&gt; FAILED to restart — manual intervention needed"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
      | &lt;span class="nb"&gt;tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOG_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$NOTIFY_EMAIL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
      &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SERVICE&lt;/span&gt;&lt;span class="s2"&gt; failed to restart on &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;hostname&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; at &lt;/span&gt;&lt;span class="nv"&gt;$DATE&lt;/span&gt;&lt;span class="s2"&gt;. Check: journalctl -u &lt;/span&gt;&lt;span class="nv"&gt;$SERVICE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
        | mail &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"[CRITICAL] &lt;/span&gt;&lt;span class="nv"&gt;$SERVICE&lt;/span&gt;&lt;span class="s2"&gt; restart failed"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$NOTIFY_EMAIL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;fi
  fi
fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Why &lt;code&gt;systemctl is-active --quiet&lt;/code&gt; and Not Something Else
&lt;/h2&gt;

&lt;p&gt;I've seen people use &lt;code&gt;ps aux | grep nginx&lt;/code&gt; for this. Don't. Here's why:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ps aux | grep nginx&lt;/code&gt; has a classic gotcha — the &lt;code&gt;grep&lt;/code&gt; command itself shows up in the results because the word "nginx" is in the grep command line. People "fix" this with &lt;code&gt;grep -v grep&lt;/code&gt; which works but is fragile and ugly. You're parsing process tables to answer a question that systemd already tracks natively.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;systemctl is-active --quiet "$SERVICE"&lt;/code&gt; asks systemd directly: "is this unit in the active state?" The &lt;code&gt;--quiet&lt;/code&gt; flag suppresses output and just returns an exit code. &lt;code&gt;0&lt;/code&gt; means active. Anything else means it's not running. Clean, reliable, no string parsing.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Two-Level Failure Check
&lt;/h2&gt;

&lt;p&gt;This isn't just "is it down → restart it." There are two separate failure modes:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Level 1:&lt;/strong&gt; Is the service running? If yes, print the check mark and exit. No log noise, no wasted disk.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Level 2:&lt;/strong&gt; If the service is down and we try to restart it — did the restart actually work? &lt;code&gt;systemctl start&lt;/code&gt; can fail for a dozen reasons: masked unit, broken config file, dependency that's also down, port already in use by something else. The script checks the exit code of the start command and sends a different email depending on whether recovery succeeded or failed.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;[RECOVERED]&lt;/code&gt; email means the script fixed it and you can keep sleeping. The &lt;code&gt;[CRITICAL]&lt;/code&gt; email means something is actually broken and you need to look at it. That distinction matters at 3 AM.&lt;/p&gt;




&lt;h2&gt;
  
  
  Setting It Up with Cron
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;crontab &lt;span class="nt"&gt;-e&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add this line:&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="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; /home/user/service-watchdog.sh &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /var/log/watchdog-cron.log 2&amp;gt;&amp;amp;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That runs every single minute. Is that overkill? Maybe. But the script finishes in under 100ms when the service is healthy, and the alternative is 6 hours of downtime on a Saturday night. I'll take the overkill.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One gotcha with cron and sudo:&lt;/strong&gt; cron runs with a minimal environment and no terminal. If &lt;code&gt;sudo systemctl start&lt;/code&gt; prompts for a password, it hangs silently forever. You need a sudoers rule:&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;sudo &lt;/span&gt;visudo
&lt;span class="c"&gt;# Add this line:&lt;/span&gt;
youruser &lt;span class="nv"&gt;ALL&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;ALL&lt;span class="o"&gt;)&lt;/span&gt; NOPASSWD: /bin/systemctl start nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or just run the watchdog cron as root.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Else I Watch With This
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;SERVICE&lt;/code&gt; variable takes any systemd unit name. I run separate copies for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;nginx&lt;/code&gt; — the web server&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;mysql&lt;/code&gt; or &lt;code&gt;mariadb&lt;/code&gt; — the database&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;docker&lt;/code&gt; — the container daemon&lt;/li&gt;
&lt;li&gt;Custom services: &lt;code&gt;my-node-app.service&lt;/code&gt;, &lt;code&gt;redis-server&lt;/code&gt;, &lt;code&gt;postgresql&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want to watch multiple services in one script, loop through them:&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="nv"&gt;SERVICES&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;&lt;span class="s2"&gt;"nginx"&lt;/span&gt; &lt;span class="s2"&gt;"mysql"&lt;/span&gt; &lt;span class="s2"&gt;"redis-server"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;SERVICE &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SERVICES&lt;/span&gt;&lt;span class="p"&gt;[@]&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="c"&gt;# ... same check logic ...&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But I prefer separate scripts per service because the logs stay clean and each one can have a different notification strategy.&lt;/p&gt;




&lt;h2&gt;
  
  
  Pairing This With Other Scripts
&lt;/h2&gt;

&lt;p&gt;This watchdog handles the restart. But if you also want to know &lt;em&gt;why&lt;/em&gt; the service died, pair it with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://bashsnippets.xyz/snippets/monitor-cpu-ram-usage.html" rel="noopener noreferrer"&gt;Monitor CPU &amp;amp; RAM Usage&lt;/a&gt; — catches the OOM conditions that kill services in the first place&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://bashsnippets.xyz/snippets/bash-send-email-alert.html" rel="noopener noreferrer"&gt;Send Email Alert from Bash&lt;/a&gt; — the email sending setup if you've never configured &lt;code&gt;mail&lt;/code&gt; on Linux&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Between these three scripts, you've got a basic monitoring stack that runs entirely on cron and costs nothing.&lt;/p&gt;




&lt;p&gt;Full script, the line-by-line breakdown, cron setup walkthrough, and three more variations:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://bashsnippets.xyz/snippets/restart-service-if-stopped.html" rel="noopener noreferrer"&gt;bashsnippets.xyz/snippets/restart-service-if-stopped.html&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you're managing any Linux server with services that need to stay up, this takes 5 minutes to deploy and runs quietly forever.&lt;/p&gt;

</description>
      <category>bash</category>
      <category>linux</category>
      <category>devops</category>
      <category>productivity</category>
    </item>
    <item>
      <title>I Built a Free Tools Directory for Linux Users Who Are Tired of Googling the Same Commands</title>
      <dc:creator>Anguishe</dc:creator>
      <pubDate>Wed, 20 May 2026 23:01:15 +0000</pubDate>
      <link>https://dev.to/bashsnippets/i-built-a-free-tools-directory-for-linux-users-who-are-tired-of-googling-the-same-commands-3ocl</link>
      <guid>https://dev.to/bashsnippets/i-built-a-free-tools-directory-for-linux-users-who-are-tired-of-googling-the-same-commands-3ocl</guid>
      <description>&lt;p&gt;I Google "bash remove file extension" at least once a month.&lt;/p&gt;

&lt;p&gt;Every time I need &lt;code&gt;${filename%.txt}&lt;/code&gt; I have to look it up. I've written that exact syntax probably 40 times across different scripts. My brain refuses to memorize it. And every time I need it again, I open a browser tab, type the question, scroll past three blog posts with 800-word intros about "the power of parameter expansion," and finally find the one-liner I need.&lt;/p&gt;

&lt;p&gt;It takes 90 seconds. It shouldn't.&lt;/p&gt;

&lt;p&gt;So I started building tools that solve problems at the exact moment I'm annoyed enough to fix them.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem with Most Bash Resources
&lt;/h2&gt;

&lt;p&gt;When you're debugging a script that's failing right now, you don't want a tutorial. You don't want to "learn the fundamentals." You want the answer in the next 30 seconds or you're going to lose your mind.&lt;/p&gt;

&lt;p&gt;Most bash resources are written for people who have time. They assume you're sitting down with a cup of coffee to &lt;em&gt;learn bash scripting&lt;/em&gt; as a general topic. They start with "What is bash?" and work up from there.&lt;/p&gt;

&lt;p&gt;But that's not how real work happens.&lt;/p&gt;

&lt;p&gt;Real work is: your cron job didn't run and you have no idea why. Your script is exiting with code 127 and you don't know what that means. You need to make a file executable for the owner but not readable by others and you can't remember if that's &lt;code&gt;chmod 700&lt;/code&gt; or &lt;code&gt;chmod 600&lt;/code&gt; or something else entirely.&lt;/p&gt;

&lt;p&gt;You're not browsing for education. You're troubleshooting something that's on fire right now.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Tools I Wish Had Existed
&lt;/h2&gt;

&lt;p&gt;Here's what I've built so far, and more importantly, &lt;em&gt;why&lt;/em&gt; each one exists:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. &lt;a href="https://bashsnippets.xyz/tools/path-debugger.html" rel="noopener noreferrer"&gt;Bash $PATH Debugger&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; I wasted 2 hours on a "command not found" error that made absolutely no sense.&lt;/p&gt;

&lt;p&gt;I had a cron job that ran &lt;code&gt;rsync&lt;/code&gt; to back up a directory. The script worked perfectly when I ran it manually. It failed silently when cron executed it. I checked the shebang. I checked permissions. I added logging. I even ran the script as the cron user manually — still worked.&lt;/p&gt;

&lt;p&gt;Turns out cron runs with a minimal &lt;code&gt;PATH&lt;/code&gt; that doesn't include &lt;code&gt;/usr/local/bin&lt;/code&gt;, where my Homebrew-installed &lt;code&gt;rsync&lt;/code&gt; lived. The script worked in my terminal because my shell's &lt;code&gt;PATH&lt;/code&gt; included it. It failed in cron because cron's &lt;code&gt;PATH&lt;/code&gt; didn't.&lt;/p&gt;

&lt;p&gt;I spent those 2 hours Googling variations of "bash script works manually fails in cron" before I finally found a StackOverflow post explaining &lt;code&gt;PATH&lt;/code&gt; inheritance in cron environments.&lt;/p&gt;

&lt;p&gt;After I fixed it, I thought: "This is going to bite me again in 6 months when I've forgotten the lesson."&lt;/p&gt;

&lt;p&gt;So I built the PATH debugger. You paste your &lt;code&gt;$PATH&lt;/code&gt;, and it shows you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which directories actually exist on your system&lt;/li&gt;
&lt;li&gt;Which are missing or broken&lt;/li&gt;
&lt;li&gt;A cleaned &lt;code&gt;export PATH=...&lt;/code&gt; line you can copy&lt;/li&gt;
&lt;li&gt;Why common commands might fail (&lt;code&gt;rsync&lt;/code&gt; not in any directory, &lt;code&gt;python&lt;/code&gt; shadowed by a version in the wrong spot, etc.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now when someone tells me their script works manually but not in cron, I send them the tool. Problem identified in 30 seconds instead of 2 hours.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. &lt;a href="https://bashsnippets.xyz/tools/cron-job-builder.html" rel="noopener noreferrer"&gt;Cron Job Builder&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; I got cron syntax wrong three times in one week and broke production backups.&lt;/p&gt;

&lt;p&gt;First attempt: &lt;code&gt;* 2 * * *&lt;/code&gt; (which runs every minute during the 2am hour, not once at 2am).&lt;br&gt;&lt;br&gt;
Second attempt: Forgot to make the script executable, so cron failed silently.&lt;br&gt;&lt;br&gt;
Third attempt: The script ran but wrote to the wrong path because I didn't know cron doesn't expand &lt;code&gt;~&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;After the third time I had to SSH into the server at 6am to figure out why backups hadn't run, I built the cron builder.&lt;/p&gt;

&lt;p&gt;You pick the schedule visually (daily at 3am, every 6 hours, first Monday of the month, whatever), and it gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The exact cron expression&lt;/li&gt;
&lt;li&gt;The next 10 run times so you can verify it's actually what you want&lt;/li&gt;
&lt;li&gt;A production-safe wrapper that includes logging, lock files, and absolute paths&lt;/li&gt;
&lt;li&gt;A checklist of things that break (executable bit, &lt;code&gt;PATH&lt;/code&gt;, environment variables)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now I don't play cron roulette. I use the tool, copy the output, paste it into &lt;code&gt;crontab -e&lt;/code&gt;, and it works the first time.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. &lt;a href="https://bashsnippets.xyz/tools/chmod-permissions-builder.html" rel="noopener noreferrer"&gt;chmod Permissions Builder&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; I cannot hold octal permissions in my head.&lt;/p&gt;

&lt;p&gt;Every single time I need to set file permissions, I have to look up whether &lt;code&gt;700&lt;/code&gt; is owner-only or if that's &lt;code&gt;600&lt;/code&gt;, and whether &lt;code&gt;644&lt;/code&gt; is the one where others can read or if I'm thinking of &lt;code&gt;755&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I've been using Linux for years. This should be automatic by now. It's not.&lt;/p&gt;

&lt;p&gt;So I built a checkbox interface. You click read/write/execute for owner/group/others, and it shows you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The octal number (&lt;code&gt;chmod 755 script.sh&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;The symbolic syntax (&lt;code&gt;chmod u=rwx,g=rx,o=rx script.sh&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;What those permissions actually mean in plain English&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I've used this tool probably 30+ times since I built it. It's bookmarked on my work machine. I am not embarrassed about this.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. &lt;a href="https://bashsnippets.xyz/tools/bash-exit-code-lookup.html" rel="noopener noreferrer"&gt;Bash Exit Code Lookup&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; Your script exits with code 127, and you have no idea what that means.&lt;/p&gt;

&lt;p&gt;Exit codes are one of those things where everyone knows &lt;code&gt;0&lt;/code&gt; means success and &lt;code&gt;1&lt;/code&gt; means generic failure, but beyond that it's trivia. What's 126? What's 130? What's the difference between 1 and 2?&lt;/p&gt;

&lt;p&gt;The tool shows you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What the code means (127 = command not found, 126 = permission denied, 130 = terminated by Ctrl+C)&lt;/li&gt;
&lt;li&gt;Common causes&lt;/li&gt;
&lt;li&gt;Copy-paste error handlers in three styles: &lt;code&gt;if/else&lt;/code&gt;, &lt;code&gt;case&lt;/code&gt;, and inline &lt;code&gt;||&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now when a script fails with a cryptic exit code, you don't have to Google "bash exit code 127" and scroll through five blog posts. You paste the number, get the answer, copy the error handler, move on.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. &lt;a href="https://bashsnippets.xyz/tools/bash-boilerplate-generator.html" rel="noopener noreferrer"&gt;Bash Boilerplate Generator&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; Every script starts with the same 15 lines, and I'm tired of typing them.&lt;/p&gt;

&lt;p&gt;Shebang. &lt;code&gt;set -euo pipefail&lt;/code&gt;. &lt;code&gt;CHECK&lt;/code&gt; and &lt;code&gt;CROSS&lt;/code&gt; variables. A header comment explaining what the script does. Sometimes argument parsing. Sometimes a trap handler for cleanup.&lt;/p&gt;

&lt;p&gt;I was copy-pasting from an old script, deleting the parts I didn't need, and modifying what was left. That's fine for one script. It's tedious when you're writing scripts regularly.&lt;/p&gt;

&lt;p&gt;So I built a generator. You pick what you need (strict error handling, argument parsing, logging, cleanup trap), optionally inject a snippet from the library (disk space check, file backup, whatever), and it gives you a complete &lt;code&gt;.sh&lt;/code&gt; file ready to edit.&lt;/p&gt;

&lt;p&gt;No blank file. No "where do I even start." Just a working script skeleton you can build on top of.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Coming Next
&lt;/h2&gt;

&lt;p&gt;I'm not building "every bash tool imaginable." I'm building the ones I keep needing.&lt;/p&gt;

&lt;p&gt;Next up:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Shellcheck Error Decoder&lt;/strong&gt; — I'm tired of seeing &lt;code&gt;SC2086&lt;/code&gt; in my terminal and having to open a browser tab to figure out what the hell it means. Every modern bash tutorial tells you to run &lt;code&gt;shellcheck&lt;/code&gt;. Every CI pipeline includes it. But the error codes are cryptic. This tool will decode them on the spot.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bash String Manipulation Builder&lt;/strong&gt; — The &lt;code&gt;${filename%.txt}&lt;/code&gt; problem I mentioned at the start. Parameter expansion is powerful and completely impossible to remember. Click what you want (remove extension, extract directory, lowercase the string), get the syntax.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Process Signal Reference&lt;/strong&gt; — I have a note in my phone that says "SIGTERM = 15, SIGKILL = 9, SIGHUP = 1" because I can never remember which is which when a process won't die. Time to build the thing.&lt;/p&gt;

&lt;p&gt;Maybe eventually if traffic justifies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bash Array Builder (because array syntax is different from literally every other language)&lt;/li&gt;
&lt;li&gt;Redirect &amp;amp; Pipe Visualizer (I still think for 3 seconds every time I see &lt;code&gt;2&amp;gt;&amp;amp;1&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Philosophy
&lt;/h2&gt;

&lt;p&gt;These tools exist at the exact moment of maximum frustration.&lt;/p&gt;

&lt;p&gt;You're not browsing the site for fun. You're here because something is broken &lt;em&gt;right now&lt;/em&gt; and you need a fix &lt;em&gt;right now&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;That's the filter: &lt;strong&gt;If the tool solves a problem that's currently on fire, I build it.&lt;/strong&gt; If it's "nice to know" or "general education," it doesn't make the cut.&lt;/p&gt;

&lt;p&gt;I'm not trying to teach you bash. I'm trying to save you the 90 seconds you'd spend Googling the same thing you've Googled six times before.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why I'm Building This in Public
&lt;/h2&gt;

&lt;p&gt;I could keep these tools to myself. They're mostly for me anyway — shortcuts to problems I keep running into.&lt;/p&gt;

&lt;p&gt;But I remember being the person who spent 2 hours on a PATH issue that should've taken 2 minutes. I remember breaking production cron jobs because the syntax is impossible to remember. I remember the feeling of Googling the same bash question for the third time this month and wondering if I'm just bad at this.&lt;/p&gt;

&lt;p&gt;You're not bad at this. Bash syntax is inconsistent and hard to memorize, and the official documentation is written for people who already know what they're looking for.&lt;/p&gt;

&lt;p&gt;If these tools save someone else those 2 hours or that feeling, that's worth making them public.&lt;/p&gt;




&lt;p&gt;All tools are free, no signup, no limits:&lt;br&gt;&lt;br&gt;
&lt;strong&gt;&lt;a href="https://bashsnippets.xyz/tools" rel="noopener noreferrer"&gt;bashsnippets.xyz/tools&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And if you need actual scripts — monitoring, backups, automation, error handling — all the snippet pages are here:&lt;br&gt;&lt;br&gt;
&lt;strong&gt;&lt;a href="https://bashsnippets.xyz/snippets" rel="noopener noreferrer"&gt;bashsnippets.xyz/snippets&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you've built tools to solve your own repetitive problems, or if you have a bash thing you Google every month like I do, drop a comment. I'm curious what other people keep having to look up.&lt;/p&gt;

</description>
      <category>bash</category>
      <category>linux</category>
      <category>opensource</category>
      <category>webdev</category>
    </item>
    <item>
      <title>I Lost a Client's Database on a $5 VPS. Here's the 12-Line Script That Would Have Saved It.</title>
      <dc:creator>Anguishe</dc:creator>
      <pubDate>Wed, 20 May 2026 17:56:29 +0000</pubDate>
      <link>https://dev.to/bashsnippets/i-lost-a-clients-database-on-a-5-vps-heres-the-12-line-script-that-would-have-saved-it-3j9j</link>
      <guid>https://dev.to/bashsnippets/i-lost-a-clients-database-on-a-5-vps-heres-the-12-line-script-that-would-have-saved-it-3j9j</guid>
      <description>&lt;p&gt;It was a WordPress site on a $5 DigitalOcean droplet. Client's small business. Nothing fancy.&lt;/p&gt;

&lt;p&gt;I had SSH access. I had root. I had every tool I needed to set up automated backups. I just... didn't. Because it was a small site. Because I'd "get to it later." Because the database was only 40MB and what could go wrong?&lt;/p&gt;

&lt;p&gt;What went wrong was a botched plugin update that corrupted the wp_options table. No backup. No snapshot. No dump. Just a client on the phone asking why their site shows a white screen and me trying to explain that their content might be gone.&lt;/p&gt;

&lt;p&gt;I rebuilt it from a cached Google version and a Wayback Machine scrape. Took two days. Billed zero.&lt;/p&gt;

&lt;p&gt;That was the last time I ran a database without automated backups.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Script
&lt;/h2&gt;

&lt;p&gt;This is what I use now. Every server, every database, no exceptions.&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="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="nv"&gt;CHECK&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"✓"&lt;/span&gt;
&lt;span class="nv"&gt;CROSS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"✗"&lt;/span&gt;

&lt;span class="c"&gt;# --- Configuration ---&lt;/span&gt;
&lt;span class="nv"&gt;DB_USER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"root"&lt;/span&gt;
&lt;span class="nv"&gt;DB_PASS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your_password"&lt;/span&gt;          &lt;span class="c"&gt;# Or use ~/.my.cnf for security&lt;/span&gt;
&lt;span class="nv"&gt;DB_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your_database"&lt;/span&gt;          &lt;span class="c"&gt;# Change to your DB name&lt;/span&gt;
&lt;span class="nv"&gt;BACKUP_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/var/backups/mysql"&lt;/span&gt;
&lt;span class="nv"&gt;KEEP_DAYS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;7                       &lt;span class="c"&gt;# Delete backups older than this&lt;/span&gt;
&lt;span class="nv"&gt;DATE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="s1"&gt;'+%Y-%m-%d_%H-%M-%S'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;FILENAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DB_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DATE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.sql.gz"&lt;/span&gt;

&lt;span class="c"&gt;# --- Create backup directory if it doesn't exist ---&lt;/span&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BACKUP_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# --- Run the backup ---&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Backing up database: &lt;/span&gt;&lt;span class="nv"&gt;$DB_NAME&lt;/span&gt;&lt;span class="s2"&gt;..."&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;mysqldump &lt;span class="nt"&gt;-u&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DB_USER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DB_PASS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DB_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;gzip&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BACKUP_DIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;FILENAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CHECK&lt;/span&gt;&lt;span class="s2"&gt; Backup created: &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BACKUP_DIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;FILENAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CHECK&lt;/span&gt;&lt;span class="s2"&gt; Size: &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;du&lt;/span&gt; &lt;span class="nt"&gt;-sh&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BACKUP_DIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;FILENAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;cut&lt;/span&gt; &lt;span class="nt"&gt;-f1&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;else
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CROSS&lt;/span&gt;&lt;span class="s2"&gt; Backup FAILED for &lt;/span&gt;&lt;span class="nv"&gt;$DB_NAME&lt;/span&gt;&lt;span class="s2"&gt; — check credentials and database name"&lt;/span&gt;
  &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="c"&gt;# --- Delete old backups ---&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Removing backups older than &lt;/span&gt;&lt;span class="nv"&gt;$KEEP_DAYS&lt;/span&gt;&lt;span class="s2"&gt; days..."&lt;/span&gt;
find &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BACKUP_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s2"&gt;"*.sql.gz"&lt;/span&gt; &lt;span class="nt"&gt;-mtime&lt;/span&gt; +&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$KEEP_DAYS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-delete&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CHECK&lt;/span&gt;&lt;span class="s2"&gt; Cleanup complete"&lt;/span&gt;

&lt;span class="c"&gt;# --- Show current backups ---&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Current backups:"&lt;/span&gt;
&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-lh&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BACKUP_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;/&lt;span class="k"&gt;*&lt;/span&gt;.sql.gz 2&amp;gt;/dev/null &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"No backups found"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. &lt;code&gt;mysqldump&lt;/code&gt; → &lt;code&gt;gzip&lt;/code&gt; → timestamp → auto-cleanup. Runs in under a second on most databases.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Each Piece Does
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;mysqldump -u"$DB_USER" -p"$DB_PASS" "$DB_NAME"&lt;/code&gt;&lt;/strong&gt; — dumps every table, every row, every schema definition into a single SQL file. This is the standard MySQL backup tool and it's been reliable for decades.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;| gzip&lt;/code&gt;&lt;/strong&gt; — pipes the dump straight into compression without ever writing an uncompressed file to disk. A 200MB database compresses to maybe 15MB. On a cheap VPS with limited storage, this matters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;date '+%Y-%m-%d_%H-%M-%S'&lt;/code&gt;&lt;/strong&gt; — timestamps every backup file. You get filenames like &lt;code&gt;mydb_2026-05-20_03-00-01.sql.gz&lt;/code&gt;. When something breaks, you can see exactly which point in time each backup represents.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;find ... -mtime +"$KEEP_DAYS" -delete&lt;/code&gt;&lt;/strong&gt; — the line most people forget. Without this, your backup folder grows forever. &lt;code&gt;-mtime +7&lt;/code&gt; means "modified more than 7 days ago." Adjust &lt;code&gt;KEEP_DAYS&lt;/code&gt; based on how much disk you have.&lt;/p&gt;




&lt;h2&gt;
  
  
  Stop Hardcoding Passwords
&lt;/h2&gt;

&lt;p&gt;The script above works, but &lt;code&gt;DB_PASS="your_password"&lt;/code&gt; sitting in a plain text file is a liability. In production, use a &lt;code&gt;~/.my.cnf&lt;/code&gt; file instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[client]&lt;/span&gt;
&lt;span class="py"&gt;user&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;root&lt;/span&gt;
&lt;span class="py"&gt;password&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;yourpassword&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then &lt;code&gt;chmod 600 ~/.my.cnf&lt;/code&gt; and remove the &lt;code&gt;-u&lt;/code&gt; and &lt;code&gt;-p&lt;/code&gt; flags from the &lt;code&gt;mysqldump&lt;/code&gt; command entirely. MySQL reads credentials from that file automatically. Nobody sees the password in &lt;code&gt;ps aux&lt;/code&gt; output, nobody finds it in your bash history.&lt;/p&gt;




&lt;h2&gt;
  
  
  Schedule It
&lt;/h2&gt;

&lt;p&gt;Open &lt;code&gt;crontab -e&lt;/code&gt; and add:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;0 3 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; /home/user/mysql-backup.sh &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /var/log/mysql-backup.log 2&amp;gt;&amp;amp;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That runs the backup every night at 3 AM and logs the output. If you want it every 6 hours instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;0 &lt;span class="k"&gt;*&lt;/span&gt;/6 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; /home/user/mysql-backup.sh &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /var/log/mysql-backup.log 2&amp;gt;&amp;amp;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're not comfortable with cron syntax, I built a &lt;a href="https://bashsnippets.xyz/tools/cron-job-builder.html" rel="noopener noreferrer"&gt;free cron job builder&lt;/a&gt; that generates the line for you — click the schedule you want, copy the output.&lt;/p&gt;




&lt;h2&gt;
  
  
  Variations I Actually Use
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Back up ALL databases on the server:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mysqldump &lt;span class="nt"&gt;--all-databases&lt;/span&gt; | &lt;span class="nb"&gt;gzip&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BACKUP_DIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/all_dbs_&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DATE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.sql.gz"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Copy the backup to a remote server (so it survives a full disk failure):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;scp &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BACKUP_DIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;FILENAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; backupuser@remote-server:/backups/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Send yourself an email when the backup runs (or fails):&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Pair this with my &lt;a href="https://bashsnippets.xyz/snippets/bash-send-email-alert.html" rel="noopener noreferrer"&gt;email alert script&lt;/a&gt; and you'll get a notification either way.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Part Nobody Mentions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Test your restores.&lt;/strong&gt; A backup you've never restored is a backup that might not work. Once a month, spin up a test database and run:&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;gunzip&lt;/span&gt; &amp;lt; your_backup.sql.gz | mysql &lt;span class="nt"&gt;-u&lt;/span&gt; root &lt;span class="nt"&gt;-p&lt;/span&gt; test_database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If that command works, your backups are real. If it doesn't, you've been collecting dead files.&lt;/p&gt;




&lt;p&gt;The full script with the line-by-line breakdown, the &lt;code&gt;~/.my.cnf&lt;/code&gt; setup walkthrough, and three more variations is at:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://bashsnippets.xyz/snippets/mysql-database-backup.html" rel="noopener noreferrer"&gt;bashsnippets.xyz/snippets/mysql-database-backup.html&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you're running a database on a VPS without automated backups, take 90 seconds and set this up today. Future you will be grateful.&lt;/p&gt;

</description>
      <category>bash</category>
      <category>linux</category>
      <category>mysql</category>
      <category>devops</category>
    </item>
    <item>
      <title>The Bash $PATH Debugger I Run Whenever I Get "Command Not Found"</title>
      <dc:creator>Anguishe</dc:creator>
      <pubDate>Fri, 08 May 2026 18:04:51 +0000</pubDate>
      <link>https://dev.to/bashsnippets/the-bash-path-debugger-i-run-whenever-i-get-command-not-found-50gc</link>
      <guid>https://dev.to/bashsnippets/the-bash-path-debugger-i-run-whenever-i-get-command-not-found-50gc</guid>
      <description>&lt;p&gt;Every experienced Linux user has been there: you install something, or move directories around, and suddenly &lt;code&gt;command not found&lt;/code&gt; appears for tools you know are installed.&lt;/p&gt;

&lt;p&gt;You check &lt;code&gt;which&lt;/code&gt;, &lt;code&gt;type&lt;/code&gt;, echo &lt;code&gt;$PATH&lt;/code&gt;, and still waste 10-20 minutes hunting down the issue. I've done it more times than I care to admit — especially after fresh server setups or when cleaning up dotfiles.&lt;/p&gt;

&lt;p&gt;That's why I built (and now constantly use) a &lt;strong&gt;Bash $PATH Debugger&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Paste your $PATH and get instant diagnosis
&lt;/h3&gt;

&lt;p&gt;Here's how it works in practice:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Copy your current PATH: &lt;code&gt;echo $PATH&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Paste it into the tool&lt;/li&gt;
&lt;li&gt;Get a full report + fixed version in seconds&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Try it here:&lt;/strong&gt; &lt;a href="https://bashsnippets.xyz/tools/path-debugger.html" rel="noopener noreferrer"&gt;Bash $PATH Debugger&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What the tool does
&lt;/h3&gt;

&lt;p&gt;The debugger takes your raw &lt;code&gt;$PATH&lt;/code&gt; string and analyzes every directory with these checks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Existence&lt;/strong&gt;: Does the directory actually exist on the filesystem?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Duplicates&lt;/strong&gt;: Are you wasting lookup time with repeated entries?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Permissions&lt;/strong&gt;: Are any directories missing execute permissions for your user?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Order&lt;/strong&gt;: Shows the exact search priority (first match wins)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clean output&lt;/strong&gt;: Generates a cleaned, deduplicated &lt;code&gt;export PATH=...&lt;/code&gt; line ready for &lt;code&gt;.bashrc&lt;/code&gt; or &lt;code&gt;.profile&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It even highlights common problems like missing &lt;code&gt;~/.local/bin&lt;/code&gt;, collapsed entries after moves, or directories that exist but aren't in your current session.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why this tool saves so much time
&lt;/h3&gt;

&lt;p&gt;"command not found" (exit code 127) is one of the most searched Bash errors online. Yet most resources just tell you to &lt;code&gt;export PATH=...&lt;/code&gt; manually without diagnosing &lt;em&gt;why&lt;/em&gt; it broke.&lt;/p&gt;

&lt;p&gt;This tool does the diagnosis for you. It's especially powerful on fresh VPS setups, after migrating user accounts, or when working with Docker/container environments where PATH behaves differently.&lt;/p&gt;

&lt;h3&gt;
  
  
  Real-world use cases
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;After a &lt;code&gt;apt install&lt;/code&gt; or &lt;code&gt;brew install&lt;/code&gt; that doesn't add itself to PATH properly&lt;/li&gt;
&lt;li&gt;Debugging why a script works in one terminal but not another&lt;/li&gt;
&lt;li&gt;Cleaning up an overloaded PATH that accumulated junk over years&lt;/li&gt;
&lt;li&gt;Preparing a clean minimal PATH for production servers&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Pro tip: Test on a clean VPS
&lt;/h3&gt;

&lt;p&gt;One of my favorite workflows is spinning up a fresh $4-5/mo VPS, installing my usual tools, running them through the debugger, and copying the optimized PATH into my base server image.&lt;/p&gt;

&lt;h3&gt;
  
  
  Common variations &amp;amp; advanced usage
&lt;/h3&gt;

&lt;p&gt;The tool also supports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Relative path expansion&lt;/li&gt;
&lt;li&gt;Handling of &lt;code&gt;~&lt;/code&gt; and environment variables in PATH entries&lt;/li&gt;
&lt;li&gt;Export formats for different shells (bash, zsh, fish compatibility notes)&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Full interactive tool with live diagnosis, cleaned PATH generator, and detailed explanations:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;&lt;a href="https://bashsnippets.xyz/tools/path-debugger.html" rel="noopener noreferrer"&gt;https://bashsnippets.xyz/tools/path-debugger.html&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Paste your PATH. Fix the problem. Move on.&lt;/p&gt;

</description>
      <category>bash</category>
      <category>linux</category>
      <category>beginners</category>
      <category>automation</category>
    </item>
    <item>
      <title>I Built a Free Tools Directory for Linux Users Who Are Tired of Googling the Same Commands</title>
      <dc:creator>Anguishe</dc:creator>
      <pubDate>Thu, 07 May 2026 20:51:35 +0000</pubDate>
      <link>https://dev.to/bashsnippets/i-built-a-free-tools-directory-for-linux-users-who-are-tired-of-googling-the-same-commands-2e27</link>
      <guid>https://dev.to/bashsnippets/i-built-a-free-tools-directory-for-linux-users-who-are-tired-of-googling-the-same-commands-2e27</guid>
      <description>&lt;h2&gt;
  
  
  The Problem I Keep Running Into
&lt;/h2&gt;

&lt;p&gt;I've written the same bash script scaffolding at least 40 times. Every time I spin up a new automation, I start from scratch: add the shebang, remember the &lt;code&gt;set -euo pipefail&lt;/code&gt; line, define variables, write the same error-handling block I always use, forget to add a usage message until the third time I run it wrong.&lt;/p&gt;

&lt;p&gt;And don't get me started on chmod. I Google "chmod 755 vs 644" at least once a month, even though I &lt;em&gt;know&lt;/em&gt; what it means. The octal-to-permission mapping just doesn't stick unless you're using it every single day.&lt;/p&gt;

&lt;p&gt;So I built something to fix both of those problems — and it's turning into a whole directory of tools.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Built: bashsnippets.xyz/tools
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://bashsnippets.xyz/tools" rel="noopener noreferrer"&gt;bashsnippets.xyz/tools&lt;/a&gt;&lt;/strong&gt; is a free directory of interactive tools for Linux users and developers. No signup. No rate limits. Works in the browser. Copy and go.&lt;/p&gt;

&lt;p&gt;Right now it has two tools live, with a new one shipping every week:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. chmod Permissions Builder
&lt;/h3&gt;

&lt;p&gt;Click checkboxes for read/write/execute across owner/group/others. Get the exact &lt;code&gt;chmod&lt;/code&gt; command instantly — both octal and symbolic — with a plain-English explanation.&lt;/p&gt;

&lt;p&gt;Example output:&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;chmod &lt;/span&gt;755 filename
&lt;span class="nb"&gt;chmod &lt;/span&gt;&lt;span class="nv"&gt;u&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;rwx,g&lt;span class="o"&gt;=&lt;/span&gt;rx,o&lt;span class="o"&gt;=&lt;/span&gt;rx filename
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Owner:&lt;/strong&gt; read + write + execute&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Group:&lt;/strong&gt; read + execute&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Others:&lt;/strong&gt; read + execute&lt;/p&gt;

&lt;p&gt;If you've ever second-guessed whether a script needs 755 or 744, this eliminates the lookup step permanently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://bashsnippets.xyz/tools/chmod-permissions-builder.html" rel="noopener noreferrer"&gt;→ Try the chmod builder&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  2. Bash Boilerplate Generator
&lt;/h3&gt;

&lt;p&gt;Pick your script type (automation, backup, monitoring, deployment), toggle options (error handling, logging, dry-run mode, help message), optionally inject a snippet from the library, and get a production-ready &lt;code&gt;.sh&lt;/code&gt; template with everything wired up.&lt;/p&gt;

&lt;p&gt;No more blank files. No more copying the same header block from your last script.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://bashsnippets.xyz/tools/bash-boilerplate-generator.html" rel="noopener noreferrer"&gt;→ Try the boilerplate generator&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Coming Next
&lt;/h2&gt;

&lt;p&gt;I'm shipping a new tool every week. The queue right now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cron Job Builder&lt;/strong&gt; — visual cron syntax builder with plain-English output&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exit Code Lookup&lt;/strong&gt; — paste an exit code, get the meaning instantly&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;systemd Unit File Generator&lt;/strong&gt; — build service files without memorizing the spec&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each one solves a specific "I always Google this" problem.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why I Built This (and Why It's Free)
&lt;/h2&gt;

&lt;p&gt;I got tired of opening 6 Stack Overflow tabs every time I needed to do something I'd done 20 times before. These aren't complex problems — they're just pattern-matching tasks where the syntax is annoying to remember.&lt;/p&gt;

&lt;p&gt;The tools are free because:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;They're simple enough that paywalling them would be ridiculous&lt;/li&gt;
&lt;li&gt;I want them to be genuinely useful, not VC-funded SaaS bloat&lt;/li&gt;
&lt;li&gt;The snippet library (which the tools complement) has always been free&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There's no business model. Just a side project that scratches an itch I have, and apparently a lot of other people have too based on the YouTube Shorts I've been posting.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://bashsnippets.xyz/tools" rel="noopener noreferrer"&gt;bashsnippets.xyz/tools&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you spot bugs, missing features, or have ideas for new tools, drop a comment. I'm building this in public and genuinely want feedback from people who actually use bash day-to-day.&lt;/p&gt;

</description>
      <category>bash</category>
      <category>linux</category>
      <category>opensource</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Stop Guessing 'chmod' I Built a Free Visual Permissions Builder for Linux</title>
      <dc:creator>Anguishe</dc:creator>
      <pubDate>Thu, 07 May 2026 20:31:05 +0000</pubDate>
      <link>https://dev.to/bashsnippets/stop-guessing-chmod-i-built-a-free-visual-permissions-builder-for-linux-2m1a</link>
      <guid>https://dev.to/bashsnippets/stop-guessing-chmod-i-built-a-free-visual-permissions-builder-for-linux-2m1a</guid>
      <description>&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;You're about to deploy a script. You know you need the file executable, but readable only by the owner. You type &lt;code&gt;chmod 754&lt;/code&gt;... or was it &lt;code&gt;744&lt;/code&gt;? Maybe &lt;code&gt;755&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;You Google it. You find a Stack Overflow thread from 2011. You get it wrong anyway.&lt;/p&gt;

&lt;p&gt;chmod's octal notation is one of those things every Linux user has to look up every single time — and the mental model for &lt;em&gt;why&lt;/em&gt; &lt;code&gt;755&lt;/code&gt; means what it means isn't obvious unless you've memorized the bit table.&lt;/p&gt;

&lt;p&gt;I built a tool to fix that permanently.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Tool: chmod Permissions Builder
&lt;/h2&gt;

&lt;p&gt;🔗 &lt;strong&gt;&lt;a href="https://bashsnippets.xyz/tools/chmod-permissions-builder.html" rel="noopener noreferrer"&gt;bashsnippets.xyz/tools/chmod-permissions-builder.html&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It's a free, no-login, browser-based tool. You click checkboxes for &lt;strong&gt;Owner&lt;/strong&gt;, &lt;strong&gt;Group&lt;/strong&gt;, and &lt;strong&gt;Others&lt;/strong&gt; — read, write, execute — and it instantly generates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;chmod&lt;/code&gt; octal command (&lt;code&gt;chmod 755 filename&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;The symbolic command (&lt;code&gt;chmod u=rwx,g=rx,o=rx filename&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;A plain-English explanation of what those permissions mean&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No ads walls. No email required. Copy and go.&lt;/p&gt;




&lt;h2&gt;
  
  
  How chmod Permissions Actually Work
&lt;/h2&gt;

&lt;p&gt;Before we get to the tool output, here's the bit math that makes it click once:&lt;/p&gt;

&lt;p&gt;Each permission set (owner / group / others) is a 3-bit binary number:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Permission&lt;/th&gt;
&lt;th&gt;Binary&lt;/th&gt;
&lt;th&gt;Octal&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;---&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;000&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--x&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;001&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-w-&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;010&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-wx&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;011&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;r--&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;r-x&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;101&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;rw-&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;110&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;rwx&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;111&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;So &lt;code&gt;chmod 755&lt;/code&gt; means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;7&lt;/strong&gt; = owner gets &lt;code&gt;rwx&lt;/code&gt; (read + write + execute)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;5&lt;/strong&gt; = group gets &lt;code&gt;r-x&lt;/code&gt; (read + execute, no write)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;5&lt;/strong&gt; = others get &lt;code&gt;r-x&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's it. Once you see it as binary, it's never confusing again. The tool builds this table live as you click.&lt;/p&gt;




&lt;h2&gt;
  
  
  Common Permission Combos
&lt;/h2&gt;

&lt;p&gt;Here are the most-used values and what they're for:&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;chmod &lt;/span&gt;755 script.sh      &lt;span class="c"&gt;# Executable script — owner can edit, others can run&lt;/span&gt;
&lt;span class="nb"&gt;chmod &lt;/span&gt;644 config.conf    &lt;span class="c"&gt;# Config file — owner can edit, others read-only&lt;/span&gt;
&lt;span class="nb"&gt;chmod &lt;/span&gt;600 .ssh/id_rsa    &lt;span class="c"&gt;# Private key — owner only, no one else&lt;/span&gt;
&lt;span class="nb"&gt;chmod &lt;/span&gt;777 /tmp/shared    &lt;span class="c"&gt;# Fully open (use sparingly, never on production)&lt;/span&gt;
&lt;span class="nb"&gt;chmod &lt;/span&gt;700 ~/scripts/     &lt;span class="c"&gt;# Private directory — owner only&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  How to Apply Permissions Recursively
&lt;/h2&gt;

&lt;p&gt;The builder outputs a single-file command. If you need to apply permissions across a whole directory:&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="c"&gt;# Apply 755 to all directories, 644 to all files&lt;/span&gt;
find /var/www/html &lt;span class="nt"&gt;-type&lt;/span&gt; d &lt;span class="nt"&gt;-exec&lt;/span&gt; &lt;span class="nb"&gt;chmod &lt;/span&gt;755 &lt;span class="o"&gt;{}&lt;/span&gt; &lt;span class="se"&gt;\;&lt;/span&gt;
find /var/www/html &lt;span class="nt"&gt;-type&lt;/span&gt; f &lt;span class="nt"&gt;-exec&lt;/span&gt; &lt;span class="nb"&gt;chmod &lt;/span&gt;644 &lt;span class="o"&gt;{}&lt;/span&gt; &lt;span class="se"&gt;\;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or use the &lt;code&gt;-R&lt;/code&gt; flag (with care):&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;chmod&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; 755 /var/www/html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;⚠️ Never run &lt;code&gt;chmod -R 777&lt;/code&gt; on anything outside a throw-away sandbox. It removes all security boundaries.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Check Current Permissions Fast
&lt;/h2&gt;

&lt;p&gt;Before changing anything, inspect what's there:&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;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; filename          &lt;span class="c"&gt;# Single file&lt;/span&gt;
&lt;span class="nb"&gt;stat &lt;/span&gt;filename            &lt;span class="c"&gt;# Verbose output including octal&lt;/span&gt;
&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; /path/to/dir/     &lt;span class="c"&gt;# Directory listing&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;stat&lt;/code&gt; shows you the octal directly — useful when you need to replicate permissions from one file to another:&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;stat&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"%a"&lt;/span&gt; filename    &lt;span class="c"&gt;# Just the octal number&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Try the Tool
&lt;/h2&gt;

&lt;p&gt;If you set the wrong permissions on a web server config or SSH key, things break in ways that are slow to debug. Getting it right the first time matters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://bashsnippets.xyz/tools/chmod-permissions-builder.html" rel="noopener noreferrer"&gt;→ Open the chmod Permissions Builder&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No login. No install. Works on mobile too.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;More free tools and copy-paste bash scripts at &lt;a href="https://bashsnippets.xyz" rel="noopener noreferrer"&gt;bashsnippets.xyz&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>bash</category>
      <category>linux</category>
      <category>terminal</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Every bash script I write starts with the same 20 lines. So I made a generator for them.</title>
      <dc:creator>Anguishe</dc:creator>
      <pubDate>Wed, 06 May 2026 21:00:43 +0000</pubDate>
      <link>https://dev.to/bashsnippets/every-bash-script-i-write-starts-with-the-same-20-lines-so-i-made-a-generator-for-them-4nan</link>
      <guid>https://dev.to/bashsnippets/every-bash-script-i-write-starts-with-the-same-20-lines-so-i-made-a-generator-for-them-4nan</guid>
      <description>&lt;h2&gt;
  
  
  There's a set of things every bash script should have at the top. Not "here are some nice-to-haves" — actual things that prevent real, bad, hard-to-debug failures:
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail
&lt;span class="nv"&gt;IFS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;$'&lt;/span&gt;&lt;span class="se"&gt;\n\t&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;set -e&lt;/code&gt; makes the script exit immediately if any command returns a non-zero status. Without it, your script keeps running after a failure and you get compounding errors that are confusing to trace.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;set -u&lt;/code&gt; makes the script error if you reference a variable that hasn't been set. Without it, &lt;code&gt;$MYVAR&lt;/code&gt; being undefined silently evaluates to an empty string. That empty string goes into a path. That path gets passed to &lt;code&gt;rm -rf&lt;/code&gt;. You see where this goes.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;set -o pipefail&lt;/code&gt; makes the exit status of a pipeline reflect the first failure in the chain, not just the last command. &lt;code&gt;broken_command | grep something&lt;/code&gt; would exit 0 without this flag, because &lt;code&gt;grep&lt;/code&gt; succeeded even though the input was empty.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;IFS=$'\n\t'&lt;/code&gt; prevents word splitting on spaces in filenames. Without it, a filename like &lt;code&gt;my file.txt&lt;/code&gt; gets treated as two arguments.&lt;/p&gt;

&lt;h3&gt;
  
  
  This is what I call the mandatory header. I've forgotten parts of it enough times that I stopped trying to remember it and started generating it.
&lt;/h3&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;The Boilerplate Generator&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://bashsnippets.xyz/tools/bash-boilerplate-generator.html" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  You configure:
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Script identity&lt;/strong&gt; — name, description, author. These go in the comment block at the top so someone reading the script 6 months from now (probably you) knows what it's supposed to do.
&lt;/h3&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Safety flags&lt;/strong&gt; — each one is a checkbox with an explanation of what it does. &lt;code&gt;set -x&lt;/code&gt; is there too for debug mode — I often add it temporarily when something isn't working, then remove it before deploying.
&lt;/h3&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Features:&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Timestamped &lt;code&gt;log()&lt;/code&gt; function — stops you from using raw &lt;code&gt;echo&lt;/code&gt; for output, which doesn't timestamp. When you're looking at a log file and everything just says "backup complete" with no time attached, you'll wish you had this.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CHECK&lt;/code&gt; / &lt;code&gt;CROSS&lt;/code&gt; variables — adds &lt;code&gt;CHECK="✓"&lt;/code&gt; and &lt;code&gt;CROSS="✗"&lt;/code&gt; at the top, which is the convention across all BashSnippets scripts. Consistent visual output in the terminal.&lt;/li&gt;
&lt;li&gt;Argument parser with &lt;code&gt;--help&lt;/code&gt; and &lt;code&gt;--dry-run&lt;/code&gt; — getopts-based. Dry run mode is something I use constantly during development to test a script's logic without actually writing/deleting/moving anything.&lt;/li&gt;
&lt;li&gt;Lock file — prevents duplicate runs. If you're scheduling something with cron and the job sometimes runs long, without a lock file you can end up with two instances trying to write to the same output file.&lt;/li&gt;
&lt;li&gt;Root check — exits cleanly with an error if the script isn't run as root, instead of failing halfway through with a permission error.&lt;/li&gt;
&lt;li&gt;Trap on EXIT — runs a &lt;code&gt;cleanup()&lt;/code&gt; function on exit or interruption, which you can populate with whatever teardown logic makes sense.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Snippet injection&lt;/strong&gt; — you can pull in a script from the library (disk space warning, file backup, delete old logs, etc.) and have it dropped into the body of the generated boilerplate. So you're not starting from zero — you're starting from a working script wrapped in production safety.
&lt;/h3&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;What the output looks like&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  When you configure everything and click copy, you get a complete &lt;code&gt;.sh&lt;/code&gt; file: shebang, comment block, safety flags, feature code, and your injected snippet (if any) in the main logic section. There's even a source comment pointing back to bashsnippets.xyz so your team knows where the template came from.
&lt;/h3&gt;

&lt;h4&gt;
  
  
  I use this myself. Every new script starts here.
&lt;/h4&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Why this exists as a tool instead of a GitHub gist&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Because I kept updating my gist as I learned new things, and then had five different versions floating around in different places. A tool with checkboxes forces me to be explicit about what I'm including and why, and the output is always current.
&lt;/h3&gt;

&lt;h3&gt;
  
  
  Try it: &lt;strong&gt;&lt;a href="https://bashsnippets.xyz/tools/bash-boilerplate-generator.html" rel="noopener noreferrer"&gt;https://bashsnippets.xyz/tools/bash-boilerplate-generator.html&lt;/a&gt;&lt;/strong&gt;
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Free, no login, GitHub Pages. Generates clean &lt;code&gt;.sh&lt;/code&gt; output you can paste directly into a file and start editing.
&lt;/h4&gt;

</description>
      <category>bash</category>
      <category>linux</category>
      <category>devops</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Stop Googling "what is exit code 127" There's a better way to handle it</title>
      <dc:creator>Anguishe</dc:creator>
      <pubDate>Wed, 06 May 2026 20:57:35 +0000</pubDate>
      <link>https://dev.to/bashsnippets/stop-googling-what-is-exit-code-127-theres-a-better-way-to-handle-it-53je</link>
      <guid>https://dev.to/bashsnippets/stop-googling-what-is-exit-code-127-theres-a-better-way-to-handle-it-53je</guid>
      <description>&lt;h2&gt;
  
  
  Exit code 127 means "command not found." I know that now. But the first dozen times I saw it, I Googled it. Every time. Because exit codes are one of those things that are fundamental to bash scripting but never feel intuitive until you've been burned by them enough.
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Here's a quick table of the ones that show up constantly:
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Code&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;Success&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;General error&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Misuse of shell built-in&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;126&lt;/td&gt;
&lt;td&gt;Command found but not executable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;127&lt;/td&gt;
&lt;td&gt;Command not found&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;130&lt;/td&gt;
&lt;td&gt;Script killed with Ctrl-C&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;137&lt;/td&gt;
&lt;td&gt;Process killed with SIGKILL (kill -9)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;139&lt;/td&gt;
&lt;td&gt;Segmentation fault&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;143&lt;/td&gt;
&lt;td&gt;Process killed with SIGTERM&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h4&gt;
  
  
  The rule for codes above 128: subtract 128 to get the signal number. 137 = 128 + 9 (SIGKILL). 143 = 128 + 15 (SIGTERM). Once you know that pattern, a lot of the high-numbered codes make sense.
&lt;/h4&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;But looking up the code is only half the problem&lt;/strong&gt;
&lt;/h3&gt;

&lt;h4&gt;
  
  
  The other half is writing the error handler. And this is where most scripts fall short. They check &lt;code&gt;$?&lt;/code&gt; after a command, but the handler is usually something like:
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;$?&lt;/span&gt; &lt;span class="nt"&gt;-ne&lt;/span&gt; 0 &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"something went wrong"&lt;/span&gt;
  &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Which tells you almost nothing when you're looking at logs at 2am.
&lt;/h4&gt;

&lt;h3&gt;
  
  
  I built a tool that generates a proper handler for any code you give it.
&lt;/h3&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;The Exit Code Lookup tool&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  You go to &lt;a href="https://bashsnippets.xyz/tools/bash-exit-code-lookup.html" rel="noopener noreferrer"&gt;https://bashsnippets.xyz/tools/bash-exit-code-lookup.html&lt;/a&gt;, type in a code — or click one of the common ones — and you get:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;What the code means&lt;/li&gt;
&lt;li&gt;The most likely causes (this is the part that actually saves time)&lt;/li&gt;
&lt;li&gt;A copy-paste error handler in your choice of format: if/else block, case statement, or inline &lt;code&gt;||&lt;/code&gt; fallback&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The if/else output names the code explicitly, logs what failed, and exits with the same code so the calling process knows something went wrong. The case statement version is better if you're handling multiple possible outcomes. The inline &lt;code&gt;||&lt;/code&gt; is what you want when you're doing something quick and you just need a fast bail-out.
&lt;/h3&gt;

&lt;h3&gt;
  
  
  One thing I added that I actually use myself: the quick-click buttons for the codes you see constantly. You don't have to type 127 — just click it, get the handler, copy it.
&lt;/h3&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Common situation this actually helps with&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  You're writing a script that calls a binary. You want to handle the case where the binary isn't installed (127), the case where the binary exists but fails on the input (1), and the case where someone Ctrl-C'd it (130). Those three cases need different responses — log and exit, retry with different args, clean up and exit gracefully — and the case statement the tool generates is structured for exactly that.
&lt;/h3&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;The thing I keep adding to all my scripts now&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;trap&lt;/span&gt; &lt;span class="s1"&gt;'echo "Script failed at line $LINENO with exit code $?" &amp;gt;&amp;amp;2'&lt;/span&gt; ERR
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Put that near the top of any script that matters. When it fails — and things fail — you know exactly where and with what code. Pair it with the exit code lookup when you're not sure what the code means, and you cut debugging time significantly.
&lt;/h3&gt;




&lt;h4&gt;
  
  
  Try it: &lt;strong&gt;&lt;a href="https://bashsnippets.xyz/tools/bash-exit-code-lookup.html" rel="noopener noreferrer"&gt;https://bashsnippets.xyz/tools/bash-exit-code-lookup.html&lt;/a&gt;&lt;/strong&gt;
&lt;/h4&gt;

&lt;h4&gt;
  
  
  No signup. All 256 codes (0-255) are in there. The handlers are copy-ready and production-quality, not the half-baked one-liners you find on random Stack Overflow answers.
&lt;/h4&gt;

</description>
      <category>bash</category>
      <category>linux</category>
      <category>webdev</category>
      <category>beginners</category>
    </item>
    <item>
      <title>I got tired of deploying broken cronjobs, so I built a tool that generates them properly</title>
      <dc:creator>Anguishe</dc:creator>
      <pubDate>Wed, 06 May 2026 20:54:04 +0000</pubDate>
      <link>https://dev.to/bashsnippets/i-got-tired-of-deploying-broken-cronjobs-so-i-built-a-tool-that-generates-them-properly-3jl6</link>
      <guid>https://dev.to/bashsnippets/i-got-tired-of-deploying-broken-cronjobs-so-i-built-a-tool-that-generates-them-properly-3jl6</guid>
      <description>&lt;h1&gt;
  
  
  Every linux user has a story. Mine is a backup script that silently failed for three weeks because the cron expression was right but the PATH wasn't set, so the script couldn't find tar. Backups looked like they were running. Nothing was actually being archived. I found out when I needed to restore a file.
&lt;/h1&gt;

&lt;h1&gt;
  
  
  That specific failure mode — cron jobs that pass silently but don't actually do anything — is one of the most annoying problems in Linux automation because there's no obvious error. The job runs. The exit code is 0. Your logs say nothing because you didn't add logging. And now you're debugging something that happened at 2am three weeks ago.
&lt;/h1&gt;

&lt;h2&gt;
  
  
  So I built a tool.
&lt;/h2&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;What it does&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Cron Job Builder at bashsnippets.xyz does three things that I always forget to do manually:
&lt;/h3&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;1. Builds the expression visually&lt;/strong&gt;
&lt;/h3&gt;

&lt;h4&gt;
  
  
  You pick minute, hour, day of month, month, and day of week from dropdowns. It assembles the expression in real time. There are also quick presets — daily 2am, every 6 hours, weekdays 9am, monthly, every 15 minutes — for the schedules you actually use 90% of the time.
&lt;/h4&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;2. Wraps it in production-safe bash&lt;/strong&gt;
&lt;/h3&gt;

&lt;h4&gt;
  
  
  This is the part that actually matters. The tool adds:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;&amp;gt;&amp;gt; /var/log/cronjob.log 2&amp;gt;&amp;amp;1&lt;/code&gt; — so stdout and stderr go somewhere you can read them later&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SHELL=/bin/bash&lt;/code&gt; — because cron's default shell is not what you think it is&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PATH=/usr/local/bin:/usr/bin:/bin&lt;/code&gt; — this is the one that burned me. Cron has a stripped-down PATH. Commands that work in your terminal fail silently in cron because the binary isn't in scope.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;MAILTO=you@example.com&lt;/code&gt; — optional, but useful for catching failures you're not watching for&lt;/li&gt;
&lt;li&gt;Lock file via &lt;code&gt;flock&lt;/code&gt; — prevents a slow job from stacking on top of itself if it runs longer than the schedule interval&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Every single one of these is a checkbox. You don't have to remember them — you just check what you need.
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;3. Shows the next 10 scheduled run times&lt;/strong&gt;
&lt;/h3&gt;

&lt;h4&gt;
  
  
  So you can verify the expression is actually doing what you think it's doing before you paste it into crontab.
&lt;/h4&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;You can also inject a snippet from the library&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The tool has a dropdown that lets you pull in a pre-built script from the BashSnippets library — file backup, disk space warning, delete old logs — and have it wrapped in the boilerplate automatically. So you go from "I want to back up my home directory nightly" to a full, copy-ready crontab entry in about 20 seconds.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Why I built this on a static site&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The whole site is GitHub Pages. No backend, no database, no server-side anything. The cron expression parser, the next-run-time calculator, all of it runs in the browser. That means it loads fast, works offline if you've cached it, and I don't have to pay for compute just so someone can build a cron expression.&lt;/p&gt;




&lt;h3&gt;
  
  
  Try it: &lt;strong&gt;&lt;a href="https://bashsnippets.xyz/tools/cron-job-builder.html" rel="noopener noreferrer"&gt;https://bashsnippets.xyz/tools/cron-job-builder.html&lt;/a&gt;&lt;/strong&gt;
&lt;/h3&gt;

&lt;h3&gt;
  
  
  Everything on BashSnippets is free. No login, no email capture, no limit on how many times you use it. Just open it, configure it, copy the output.
&lt;/h3&gt;

&lt;p&gt;If you've ever had a cronjob fail silently and had to debug it at an awkward hour, this is for you.&lt;/p&gt;

</description>
      <category>bash</category>
      <category>linux</category>
      <category>devops</category>
      <category>automation</category>
    </item>
    <item>
      <title>CoderLegion? I think so. . .</title>
      <dc:creator>Anguishe</dc:creator>
      <pubDate>Wed, 06 May 2026 16:53:42 +0000</pubDate>
      <link>https://dev.to/bashsnippets/codelegion-i-think-so--998</link>
      <guid>https://dev.to/bashsnippets/codelegion-i-think-so--998</guid>
      <description>&lt;p&gt;🚀 Check out my CoderLegion profile &amp;amp; latest post!&lt;/p&gt;

&lt;p&gt;🏆 Points: 162 | 🎱 Badges: 5 | 👥 Followers: 0 | 📄 Posts: 3&lt;/p&gt;

&lt;p&gt;Latest post: "I built a free bash script library because I kept Googling the same 10 scripts over and over"&lt;br&gt;
Read it here: &lt;a href="https://coderlegion.com/16878/i-built-a-free-bash-script-library-because-i-kept-googling-the-same-10-scripts-over-and-over" rel="noopener noreferrer"&gt;https://coderlegion.com/16878/i-built-a-free-bash-script-library-because-i-kept-googling-the-same-10-scripts-over-and-over&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;View my full profile: &lt;a href="https://coderlegion.com/user/BashSnippets" rel="noopener noreferrer"&gt;https://coderlegion.com/user/BashSnippets&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Come join me!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>My site went down for a few hours yesterday and my users knew before me</title>
      <dc:creator>Anguishe</dc:creator>
      <pubDate>Mon, 04 May 2026 23:26:36 +0000</pubDate>
      <link>https://dev.to/bashsnippets/my-site-went-down-for-a-few-hours-yesterday-and-my-users-knew-before-me-1of6</link>
      <guid>https://dev.to/bashsnippets/my-site-went-down-for-a-few-hours-yesterday-and-my-users-knew-before-me-1of6</guid>
      <description>&lt;p&gt;It happened to me. &lt;/p&gt;

&lt;p&gt;My site — bashsnippets.xyz — had been down for six hours before I knew &lt;br&gt;
about it. I found out because a reader emailed me. Not because I checked. &lt;br&gt;
Not because I had monitoring. Because someone else had to tell me.&lt;/p&gt;

&lt;p&gt;That's the kind of thing that sits with you.&lt;/p&gt;

&lt;p&gt;The fix took 20 minutes to build. Here's exactly what I wrote and why &lt;br&gt;
every piece of it matters.&lt;/p&gt;


&lt;h2&gt;
  
  
  The problem with checking manually
&lt;/h2&gt;

&lt;p&gt;The natural instinct is to just open a browser and load the page. &lt;br&gt;
But that has two failure modes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You only check when you remember to&lt;/li&gt;
&lt;li&gt;You don't check at 2am when it actually goes down&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;What you want is automated, logged, and running while you sleep.&lt;/p&gt;


&lt;h2&gt;
  
  
  Start with the one-liner
&lt;/h2&gt;

&lt;p&gt;Before building the full script, understand the core command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s2"&gt;"%{http_code}"&lt;/span&gt; https://yoursite.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it. It prints one number. That number is your site's HTTP status code.&lt;/p&gt;

&lt;p&gt;Here's what each flag does — and this is worth understanding &lt;br&gt;
because you'll use these flags for a lot more than uptime checking:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;-o /dev/null&lt;/code&gt;&lt;/strong&gt; — tells curl to discard the response body entirely. &lt;br&gt;
You don't need the HTML. You only need the status code. &lt;br&gt;
Without this flag, curl dumps the entire page to your terminal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;-s&lt;/code&gt;&lt;/strong&gt; — silent mode. Suppresses the progress bar and error messages. &lt;br&gt;
Without this, curl prints download stats you don't need.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;-w "%{http_code}"&lt;/code&gt;&lt;/strong&gt; — write-out format. Tells curl to print only &lt;br&gt;
the HTTP status code after the request completes. &lt;br&gt;
This is the only output you care about.&lt;/p&gt;

&lt;p&gt;Result: just the number. Nothing else. Clean, parseable, scriptable.&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="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s2"&gt;"%{http_code}"&lt;/span&gt; https://bashsnippets.xyz
200
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Status codes you need to know
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Code&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;th&gt;What to do&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;200&lt;/td&gt;
&lt;td&gt;Up and healthy&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;301/302&lt;/td&gt;
&lt;td&gt;Redirect — usually fine&lt;/td&gt;
&lt;td&gt;Check it's intentional&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;404&lt;/td&gt;
&lt;td&gt;Page not found&lt;/td&gt;
&lt;td&gt;Check your URL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;503&lt;/td&gt;
&lt;td&gt;Server error — site is down&lt;/td&gt;
&lt;td&gt;Investigate immediately&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;000&lt;/td&gt;
&lt;td&gt;DNS failure / unreachable&lt;/td&gt;
&lt;td&gt;Your server may be completely offline&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  The full script
&lt;/h2&gt;

&lt;p&gt;Now wrap it in logic that tells you what the number means:&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="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="nv"&gt;CHECK&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"✓"&lt;/span&gt;
&lt;span class="nv"&gt;CROSS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"✗"&lt;/span&gt;

&lt;span class="nv"&gt;URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"https://bashsnippets.xyz"&lt;/span&gt;
&lt;span class="nv"&gt;STATUS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s2"&gt;"%{http_code}"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$URL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$STATUS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-eq&lt;/span&gt; 200 &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CHECK&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$URL&lt;/span&gt;&lt;span class="s2"&gt; is up (HTTP &lt;/span&gt;&lt;span class="nv"&gt;$STATUS&lt;/span&gt;&lt;span class="s2"&gt;)"&lt;/span&gt;
&lt;span class="k"&gt;else
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CROSS&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$URL&lt;/span&gt;&lt;span class="s2"&gt; returned HTTP &lt;/span&gt;&lt;span class="nv"&gt;$STATUS&lt;/span&gt;&lt;span class="s2"&gt; — check it"&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;$CHECK&lt;/code&gt; and &lt;code&gt;$CROSS&lt;/code&gt; variables aren't required — you could hardcode &lt;br&gt;
the symbols directly — but defining them at the top makes it trivial &lt;br&gt;
to change the output format later and keeps the echo lines readable &lt;br&gt;
at a glance.&lt;/p&gt;

&lt;p&gt;Save this as &lt;code&gt;uptime.sh&lt;/code&gt; in your home directory.&lt;/p&gt;


&lt;h2&gt;
  
  
  Make it executable
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x ~/uptime.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;code&gt;chmod +x&lt;/code&gt; adds execute permission to the file. Without this, &lt;br&gt;
bash treats it as a plain text file and refuses to run it.&lt;br&gt;
This is the step people forget. Every time.&lt;/p&gt;

&lt;p&gt;Test it manually before scheduling anything:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./uptime.sh
&lt;span class="c"&gt;# ✓ https://bashsnippets.xyz is up (HTTP 200)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Schedule it with cron
&lt;/h2&gt;

&lt;p&gt;A script you run manually is still manual monitoring. &lt;br&gt;
The goal is automation — the script checks your site while you sleep.&lt;/p&gt;

&lt;p&gt;Open your crontab:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;crontab &lt;span class="nt"&gt;-e&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add this line:&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="k"&gt;*&lt;/span&gt;/5 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; ~/uptime.sh &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; ~/uptime.log 2&amp;gt;&amp;amp;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Breaking this down:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;*/5 * * * *&lt;/code&gt;&lt;/strong&gt; — run every 5 minutes. The &lt;code&gt;*/5&lt;/code&gt; means "every 5th &lt;br&gt;
minute of every hour of every day." Change to &lt;code&gt;*/1&lt;/code&gt; to test it faster.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;~/uptime.sh&lt;/code&gt;&lt;/strong&gt; — the script to run. The &lt;code&gt;~/&lt;/code&gt; expands to your &lt;br&gt;
home directory — safer than a relative path.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;&amp;gt;&amp;gt; ~/uptime.log&lt;/code&gt;&lt;/strong&gt; — append output to a log file. &lt;br&gt;
Using &lt;code&gt;&amp;gt;&amp;gt;&lt;/code&gt; instead of &lt;code&gt;&amp;gt;&lt;/code&gt; means each result adds to the file &lt;br&gt;
rather than overwriting it. You want a history, not just the last result.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;2&amp;gt;&amp;amp;1&lt;/code&gt;&lt;/strong&gt; — redirect stderr to stdout so errors also get logged.&lt;/p&gt;

&lt;p&gt;Save and exit. Cron confirms: &lt;code&gt;crontab: installing new crontab&lt;/code&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Watch it run in real time
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; ~/uptime.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;code&gt;tail -f&lt;/code&gt; watches a file and prints new lines as they're added. &lt;br&gt;
Open this in a second terminal after setting up cron. &lt;br&gt;
Within a minute you'll see the script run on its own — &lt;br&gt;
no input from you, no trigger, just cron firing the script &lt;br&gt;
and the result appearing in the log automatically.&lt;/p&gt;

&lt;p&gt;That moment where the second line appears on its own — &lt;br&gt;
that's the confirmation that automation is actually working.&lt;/p&gt;


&lt;h2&gt;
  
  
  The full copy-paste version
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="nv"&gt;CHECK&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"✓"&lt;/span&gt;
&lt;span class="nv"&gt;CROSS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"✗"&lt;/span&gt;

&lt;span class="nv"&gt;URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"https://yoursite.com"&lt;/span&gt;   &lt;span class="c"&gt;# ← change this to your site&lt;/span&gt;
&lt;span class="nv"&gt;STATUS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s2"&gt;"%{http_code}"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$URL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$STATUS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-eq&lt;/span&gt; 200 &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CHECK&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$URL&lt;/span&gt;&lt;span class="s2"&gt; is up (HTTP &lt;/span&gt;&lt;span class="nv"&gt;$STATUS&lt;/span&gt;&lt;span class="s2"&gt;)"&lt;/span&gt;
&lt;span class="k"&gt;else
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CROSS&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$URL&lt;/span&gt;&lt;span class="s2"&gt; returned HTTP &lt;/span&gt;&lt;span class="nv"&gt;$STATUS&lt;/span&gt;&lt;span class="s2"&gt; — check it"&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Cron line to add with &lt;code&gt;crontab -e&lt;/code&gt;:&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="k"&gt;*&lt;/span&gt;/5 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; ~/uptime.sh &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; ~/uptime.log 2&amp;gt;&amp;amp;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Full reference page with all variations:&lt;br&gt;
→ &lt;a href="https://bashsnippets.xyz/snippets/check-if-website-is-up.html" rel="noopener noreferrer"&gt;bashsnippets.xyz/snippets/check-if-website-is-up.html&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;I post a new copy-paste bash script every week at &lt;a href="https://bashsnippets.xyz" rel="noopener noreferrer"&gt;bashsnippets.xyz&lt;/a&gt;.&lt;br&gt;
All free. No signups. &lt;a href="https://youtube.com/@BashSnippets" rel="noopener noreferrer"&gt;@BashSnippets on YouTube&lt;/a&gt; if you want to see these in 30-second form.&lt;/p&gt;

&lt;p&gt;How do you monitor your sites? I'm genuinely curious what the rest of you are running 👇&lt;/p&gt;

</description>
      <category>bash</category>
      <category>linux</category>
      <category>webdev</category>
      <category>sysadmin</category>
    </item>
  </channel>
</rss>
