271

I have the feeling that I'm missing the obvious, but have not succeeded with man [curl|wget] or google ("http" makes such a bad search term). I'm looking for a quick&dirty fix to one of our webservers that frequently fails, returning status code 500 with an error message. Once this happens, it needs to be restarted.

As the root cause seems to be hard to find, we're aiming for a quick fix, hoping that it will be enough to bridge the time until we can really fix it (the service doesn't need high availability)

The proposed solution is to create a cron job that runs every 5 minutes, checking http://localhost:8080/. If this returns with status code 500, the webserver will be restarted. The server will restart in under a minute, so there's no need to check for restarts already running.

The server in question is a ubuntu 8.04 minimal installation with just enough packages installed to run what it currently needs. There is no hard requirement to do the task in bash, but I'd like it to run in such a minimal environment without installing any more interpreters.

(I'm sufficiently familiar with scripting that the command/options to assign the http status code to an environment variable would be enough - this is what I've looked for and could not find.)

Olaf Kock
  • 46,930
  • 8
  • 59
  • 90

13 Answers13

396

I haven't tested this on a 500 code, but it works on others like 200, 302 and 404.

response=$(curl --write-out '%{http_code}' --silent --output /dev/null servername)

Note, format provided for --write-out should be quoted. As suggested by @ibai, add --head to make a HEAD only request. This will save time when the retrieval is successful since the page contents won't be transmitted.

Community
  • 1
  • 1
Dennis Williamson
  • 346,391
  • 90
  • 374
  • 439
  • 1
    Nice - thanks: I've already found --write-out, but missed the --output /dev/null. When all the content comes with it, the response code gets lost in too much information, so I simply did not see it... – Olaf Kock Feb 08 '10 at 10:02
  • 8
    Can I store both the response code and output in separate variables? I would like to echo the output when the response code is not 200 – Vaibhav Bajpai Jan 16 '14 at 13:14
  • 9
    @VaibhavBajpai: Try this: `response=$(curl --write-out \\n%{http_code} --silent --output - servername)` - the last line in the result will be the response code. – Dennis Williamson Jan 16 '14 at 14:19
  • 4
    This does not show the final request status if the result of the first request is a 3XX. For example if the returned value is a 301 redirect, then this script just stops there. If you add -IL, then you can get the final status. If you want to show all HTTP statuses for all requests, use my example below. – siliconrockstar Oct 31 '15 at 02:52
  • 2
    Working great, thanks! However in my case (https) I needed to put `--insecure` as well. – Tomasz Racia Sep 12 '16 at 14:34
  • 2
    You can use -I or --head to just make a HEAD request and avoid downloading the whole body even if it is to /dev/null. This can reduce the execution time considerably in many cases. `response=$(curl --head --write-out %{http_code} --silent --output /dev/null servername)` – ibai Nov 05 '18 at 00:09
  • As someone not so familiar (...not at all familiar) with making a HTTP request, could you clarify what `${http_code}` is meant to be here? E.g. if I wanted to send a request to http://localhost:8080, is that URL what goes in `${http_code}`? – Lou Oct 20 '21 at 08:56
  • 1
    @Lou: Note that it's a percent sign rather than a dollar sign. It's not a Bash variable, but a format code for Curl's `--write-out` option. You would put `localhost:8080` in place of `servername` in the command in my answer. – Dennis Williamson Jun 24 '22 at 21:30
  • If u still need the response body, you can save it in a file instead of showing them. – MaXi32 Dec 12 '22 at 16:22
78

I needed to demo something quickly today and came up with this. Thought I would place it here if someone needed something similar to the OP's request.

#!/bin/bash

status_code=$(curl --write-out %{http_code} --silent --output /dev/null www.bbc.co.uk/news)

if [[ "$status_code" -ne 200 ]] ; then
  echo "Site status changed to $status_code" | mail -s "SITE STATUS CHECKER" "my_email@email.com" -r "STATUS_CHECKER"
else
  exit 0
fi

This will send an email alert on every state change from 200, so it's dumb and potentially greedy. To improve this, I would look at looping through several status codes and performing different actions dependent on the result.

Chris Gillatt
  • 1,026
  • 8
  • 13
54
curl --write-out "%{http_code}\n" --silent --output /dev/null "$URL"

works. If not, you have to hit return to view the code itself.

nathan gonzalez
  • 11,817
  • 4
  • 41
  • 57
hd1
  • 33,938
  • 5
  • 80
  • 91
35

Although the accepted response is a good answer, it overlooks failure scenarios. curl will return 000 if there is an error in the request or there is a connection failure.

url='http://localhost:8080/'
status=$(curl --head --location --connect-timeout 5 --write-out %{http_code} --silent --output /dev/null ${url})
[[ $status == 500 ]] || [[ $status == 000 ]] && echo restarting ${url} # do start/restart logic

Note: this goes a little beyond the requested 500 status check to also confirm that curl can even connect to the server (i.e. returns 000).

Create a function from it:

failureCode() {
    local url=${1:-http://localhost:8080}
    local code=${2:-500}
    local status=$(curl --head --location --connect-timeout 5 --write-out %{http_code} --silent --output /dev/null ${url})
    [[ $status == ${code} ]] || [[ $status == 000 ]]
}

Test getting a 500:

failureCode http://httpbin.org/status/500 && echo need to restart

Test getting error/connection failure (i.e. 000):

failureCode http://localhost:77777 && echo need to start

Test not getting a 500:

failureCode http://httpbin.org/status/400 || echo not a failure
nicerobot
  • 9,145
  • 6
  • 42
  • 44
12

Here is my implementation, which is a bit more verbose than some of the previous answers

curl https://somewhere.com/somepath   \
--silent \
--insecure \
--request POST \
--header "your-curl-may-want-a-header" \
--data @my.input.file \
--output site.output \
--write-out %{http_code} \
  > http.response.code 2> error.messages
errorLevel=$?
httpResponse=$(cat http.response.code)


jq --raw-output 'keys | @csv' site.output | sed 's/"//g' > return.keys
hasErrors=`grep --quiet --invert errors return.keys;echo $?`

if [[ $errorLevel -gt 0 ]] || [[ $hasErrors -gt 0 ]] || [[ "$httpResponse" != "200" ]]; then
  echo -e "Error POSTing https://somewhere.com/somepath with input my.input (errorLevel $errorLevel, http response code $httpResponse)" >> error.messages
  send_exit_message # external function to send error.messages to whoever.
fi
AG6HQ
  • 514
  • 2
  • 5
  • 16
9

With netcat and awk you can handle the server response manually:

if netcat 127.0.0.1 8080 <<EOF | awk 'NR==1{if ($2 == "500") exit 0; exit 1;}'; then
GET / HTTP/1.1
Host: www.example.com

EOF

    apache2ctl restart;
fi
marco
  • 4,455
  • 1
  • 23
  • 20
8

To follow 3XX redirects and print response codes for all requests:

HTTP_STATUS="$(curl -IL --silent example.com | grep HTTP )";    
echo "${HTTP_STATUS}";
siliconrockstar
  • 3,554
  • 36
  • 33
  • 2
    The `grep` will capture all lines with "HTTP" in them. Maybe `grep -m 1 HTTP` to only grab the first match, if that's the intent, or maybe instead pipe to Awk to parse out just the result code. – tripleee Jun 16 '17 at 05:32
5

i didn't like the answers here that mix the data with the status. found this: you add the -f flag to get curl to fail and pick up the error status code from the standard status var: $?

https://unix.stackexchange.com/questions/204762/return-code-for-curl-used-in-a-command-substitution

i don't know if it's perfect for every scenario here, but it seems to fit my needs and i think it's much easier to work with

nathan g
  • 858
  • 9
  • 17
  • 1
    -f with $? only gives you the exit status which is not equal 0 when an error occured, but not the http status – dschulten Sep 19 '20 at 14:28
3

this can help to evaluate http status

var=`curl -I http://www.example.org 2>/dev/null | head -n 1 | awk -F" " '{print $2}'`
echo http:$var
Tunaki
  • 132,869
  • 46
  • 340
  • 423
Tango
  • 31
  • 1
  • 4
    `head -n 1 | awk '{stuff}'` is a bit of an antipattern, `awk 'NR==1 {stuff}'` does the same thing in one process, pure Awk. – tripleee Jun 16 '17 at 05:29
3

Another variation:

       status=$(curl -sS  -I https://www.healthdata.gov/user/login  2> /dev/null | head -n 1 | cut -d' ' -f2)
status_w_desc=$(curl -sS  -I https://www.healthdata.gov/user/login  2> /dev/null | head -n 1 | cut -d' ' -f2-)
Nam G VU
  • 33,193
  • 69
  • 233
  • 372
dkinzer
  • 32,179
  • 12
  • 66
  • 85
2

Here comes the long-winded – yet easy to understand – script, inspired by the solution of nicerobot, that only requests the response headers and avoids using IFS as suggested here. It outputs a bounce message when it encounters a response >= 400. This echo can be replaced with a bounce-script.

# set the url to probe
url='http://localhost:8080'
# use curl to request headers (return sensitive default on timeout: "timeout 500"). Parse the result into an array (avoid settings IFS, instead use read)
read -ra result <<< $(curl -Is --connect-timeout 5 "${url}" || echo "timeout 500")
# status code is second element of array "result"
status=${result[1]}
# if status code is greater than or equal to 400, then output a bounce message (replace this with any bounce script you like)
[ $status -ge 400  ] && echo "bounce at $url with status $status"
Community
  • 1
  • 1
Thomas Praxl
  • 737
  • 7
  • 18
0

To add to @DennisWilliamson comment above:

@VaibhavBajpai: Try this: response=$(curl --write-out \n%{http_code} --silent --output - servername) - the last line in the result will be the response code

You can then parse the response code from the response using something like the following, where X can signify a regex to mark the end of the response (using a json example here)

X='*\}'
code=$(echo ${response##$X})

See Substring Removal: http://tldp.org/LDP/abs/html/string-manipulation.html

user1015492
  • 155
  • 2
  • 10
  • 1
    Why would you put the pattern in a variable, and why would you use a [useless `echo`](http://www.iki.fi/era/unix/award.html#echo) to obtain the final value? Just `code=${response##*\}}` is simpler and avoids a number of common pitfalls. Also, that's a glob pattern, not a proper regular expression. – tripleee Jun 16 '17 at 05:27
0
  1. Assuming you have already implemented a stop and start script for your application. Create a script as follows which checks the http status of your application url and restarts in case of 502:

httpStatusCode=$(curl -s -o /dev/null -w "%{http_code}" https://{your_url}/) if [ $httpStatusCode = 502 ]; then sh /{path_to_folder}/stopscript.sh sh /{path_to_folder}/startscript.sh fi

  1. Implement a cron job to invoke this script every 5 mins. Assuming the script above has name checkBootAndRestart.sh. Then your crontab should look like- */5 * * * * /{path_to_folder}/checkBootAndRestart.sh
Caffeine Coder
  • 948
  • 14
  • 17