Automatically Shut Down Ubuntu when Bandwidth Exceeds some Threshold

If you've ever had to host websites, you probably know that crippling fear of waking up in the morning to find out your sites have gone viral (or worse...), and that your monthly bill has accumulated too many 0's for you to count. While services such as DigitalOcean (not an affiliate link) provide an alert system that notifies you when certain metrics have exceeded some threshold, that doesn't help you if you happen to be asleep when that happens. Below, I describe how you can use a simple bash script to monitor your system's bandwidth and trigger an automatic shutdown if it exceeds your threshold.

The actual network traffic monitoring is done using vnStat, which you can install as follows. The output of vnStat is then scraped to find out how much bandwidth has been used so far.

sudo apt-get install vnstat

Next, open up your favorite text editor, add the following code and save it to your home directory as filename.sh. Scroll down a bit for an explanation of what the code does.

#!/bin/bash

# Set a threshold in GiB
threshold=50

# Save the output of the 5th column (tx / transmitted)
out=$(vnstat --oneline | awk -F ';' '{print $5}' | awk '/GiB/ {print $1}')

# Check whether the output is not empty
if [[ $out ]] ; then

  # Compare the resulting value to the threshold
  compare=$(echo $out '>' $threshold | bc -l)
  
  # Shutdown if the comparison returned true
  if [[ $compare -eq 1 ]] ; then
    /usr/sbin/shutdown -h now
  fi
  
fi

Allow the file to be executed using following command.

sudo chmod a+x filename.sh

To automate the shutdown, you will want this script to run periodically. To do this, you can setup a cron job. Ideally, you'd want to run it every couple of minutes or every hour. To edit your crontab, use the following command:

crontab -e

Within your crontab, you can specify how often your job should run. An example is: * * * * * ~/filename.sh

The asterisks represent the minutes, hours, day of the month, month, and day of the week, respectively. Leaving an asterisk in a field indicates that you want to run it for all values that field can take.

If you want to run the script every hour, use 0 * * * * ~/filename.sh. If you want to run it every 5 minutes, use */5 * * * * ~/filename.sh. If you need a bit of help specifying your interval of choice, check out Crontab Guru.

How it works

The first line specifies that we want bash to run the script, which is the default shell of Ubuntu.

!/bin/bash

Next, define a variable called threshold that contains your threshold in GiB (or MiB if you're super greedy ;)).

threshold=50

Before describing the next line, it might be useful if you get familiar with vnStat and what it outputs. Run the following two commands: vnstat and vnstat -- oneline. The latter is perfect for parsing.

If you're unfamiliar with the pipe-operator | , it simply defines a pipeline that uses the output of every command as input to the next. Since we're interested in the summary, take the output of vnstat --oneline and pass it to AWK which is used to process text. The -F ';' argument tells AWK that the field- separator used by vnstat --oneline is a semicolon. Using the second argument you can specify which field you're interested in using the {print $5} command. In this case we're interested in the fifth field which represents the transfered bandwidth (tx). You may also be interested in the total bandwidth for that day, which is stored in the sixth field: {print $6}.

To get a slightly better understanding of what AWK does, run the following two commands in sequence using the && operator, which produces the output on two separate lines:

vnstat --oneline && vnstat --online | awk -F ';' '{print $5}'

Now play with the field number a bit to see what fields other indexes return. Note that a value of 0 returns the full output, which is why you should start counting fields from 1.

We're only interested in bandwidth that's measured in GiB, so after obtaining the field that you're interested in ({print $5} in this case), run AWK again along with a regular expression to check if it matches the term GiB:  awk '/GiB/ {print $1}'. Note that the slashes simply indicate the start and end of the regular expression. Since we're only interested in the numerical portion (the numerical first field) rather than the full 15 GiB, use {print $1}on the output to select just this part. If the server has used up 15 GiB in bandwidth, vnstat --oneline | awk -F ';' '{print $5}' | awk '/GiB/ {print $1}'  should output the value 15.

Putting it all together, results in the following variable definition:

out=$(vnstat --oneline | awk -F ';' '{print $5}' | awk '/GiB/ {print $1}' )

Since the value of the variable $out is only going to be non-null if GiB or MiB (depending on how you configured it) is matched, add the following if- statement to ensure it's not null.

if [[ $out ]] ; then
  ...
fi

You now want to compare the value of $out to that of $threshold. As these values are likely floats, you'll want to use the command-line calculator using the standard math library, which is what the bc -l stands for.

compare=$(echo $out '>' $threshold | bc -l)

The result of the comparison above is either 0 or 1, so perform an additional check to see whether the bandwidth stored in $out has in fact exceeded your threshold. -eq performs an equality check between the two values.

if [[ $compare -eq 1 ]] ; then
  ...
fi

And finally, shut the system down if the output of the comparison is equal to 1.

/usr/sbin/shutdown -h now

Phew.

A final word of warning

As with anything in life, use common sense and double check for any errors before using this in a production setting. :)

If you need any help, feel free to @ me on Twitter.