# Get APRS weather data from aprs.fi


# Basic weather data
temp="$(wget -q https://aprs.fi/weather/a/${wxstation} -O - | grep Temperature | egrep '[-+]?([0-9]*\.[0-9]+|[0-9]+)' -o)"
humidity="$(wget -q https://aprs.fi/weather/a/${wxstation} -O - | grep Humidity | egrep '[-+]?([0-9]*\.[0-9]+|[0-9]+)' -o)"
wind="$(wget -q https://aprs.fi/weather/a/${wxstation} -O - | grep Wind | egrep '[-+]?([0-9]*\.[0-9]+|[0-9]+)' -o | sed -n -e 2p)"
rain="$(wget -q https://aprs.fi/weather/a/${wxstation} -O - | grep Rain | egrep '[-+]?([0-9]*\.[0-9]+|[0-9]+)' -o | sed -n -e 1p)"

# Telemetry
radioactivity="$(wget -q https://aprs.fi/telemetry/a/${wxstation} -O - | grep Radioactivity | egrep '[-+]?([0-9]*\.[0-9]+|[0-9]+)' -o | sed -n -e 5p)"

printf "%s\n" "Temperature: ${temp}°C" "Humidity: ${humidity}%" "Wind: ${wind} m/s" "Rain: ${rain} mm/h" "Radioactivity: ${radioactivity} uSv/h"

1. Make sure .well-known and acme-challenge directories are set permissions to 755

2. Create random file test.txt in .well-known/acme-challenge/ and try to view it with browser

If all of this is good, your certificate should renew. The problem I had on my server is that even when step 1. and 2. were ok and working, I still  got permission denied message. The problem was I setup my Varnish engine to force SSL on non SSL request. When bot requested http://somedomain.com/.well-known/acme-challenge/ it was automatically redirected to https://somedomain.com/.well-known/acme-challenge/. So,

3. Make sure you disable force SSL during renewal.

Error message example:

Failed authorization procedure. s55ma.radioamater.si (http-01): urn:acme:error:unauthorized :: The client lacks sufficient authorization :: Invalid response from http://s55ma.radioamater.si/.well-known/acme-challenge/i825k_Mk8YGTTD1GOsZvMCkZ0KaRFdext04LfQdelQs: "<!DOCTYPE html>
<title>403 Forbidden</title>
<h1>Error 403 Forbidden</h1>

Enable server ports on Xastir. We need to use the command line binary xastir_udp_client which is part of Xastir. To send the data over RF, you need to have Xastir configured with your transmitter already.

More info about the mail APRS service: http://www.aprs-is.net/email.aspx

The basic string is:

xastir_udp_client XastirIP port callsing passcode -to_rf 'callsign>APRS::EMAIL    :EmailOfReceiver@something.com message'

Real example:

xastir_udp_client 2023 S55MA-10 22222 -to_rf 'S55MA-10>APRS::EMAIL    :s55ma@radioamater.si hello'

Note: You may only send one line messages of 64 total characters maximum for the message even though the documentation is saying 67. You have to put 4 white spaces between EMAIL and EmailOfReceiver, so the total lenght of EMAIL+white spaces is 9 characters.

This post is about Xastir, but on the side note, most APRS capable handhelds stations are not able of sending email messages through APRS. One portable station that’s able to do it is Kenwood TH-D72. I found this post about it. Too bad other portables are not able to do that where this feature is the most usefull, I mean, it’s not like I’m going to need aprs to email service from my home station, I’d likely need it outdoors for emergency situations or to send an email when I’m abroad and without cellular data service.

Quick script for sending messages:


#APRS to Email script using Xastir
#Define variables
#Xastir server and port

#Authentication info

echo "Enter sender (yours or some others callsign)"
read sender
sendercapital="$(echo $sender | awk '{print toupper($0)}')"

echo "Enter destination email:"
read email

echo "Enter your message (max 64 characters):"
read message

#Count message characters
msglenght="$(echo $message | wc -c)"

#Restart script if message exceeds 64 characters.
if [ "$msglenght" -gt 64 ]; then
echo "Your message exceeded 64 characters, try again!"
exec bash "$0"
xastir_udp_client $server $port $user $passcode -to_rf "$sendercapital>APRS::EMAIL    :$email $message" >/dev/null 2>&1
echo "Message has been sent."

In my previous post I wrote about sending objects and telemetry via command line to APRS-IS server.

You can also do that with Xastir, but as a bonus point, you can do it all via RF if your Xastir setup is already paired to the radio and configured to transmit. You can use the scripts from my previous blog post, you only need to change some commands.

Xastir GUI is lacking options to send multiple different beacons or telemetry data. There is a binary called xastir_udp_client that comes with normal Xastir setup which takes care of that, but you need to run it from a command line and do some custom scripting. To use this feature, you need to enable server ports. Open xastir, go to interface menu and click enable server ports.

The basic commands to create an object with a house icon are:

Send via RF:

xastir_udp_client XastirIP port user passcode -to_rf 'senduser>APN100,WIDEPATH*:=latitude/longtitude-Comment'
xastir_udp_client 2023 S55MA-10 22558 -to_rf 'S55MA-10>APN100,WIDE2-1*:=4539.94N/01417.67E-QTH'

Send via Internet only (remove -to_rf):

xastir_udp_client XastirIP port user passcode 'senduser>APN100,TCPIP*:=latitude/longtitude-Comment'
xastir_udp_client 2023 S55MA-10 22558 'S55MA-10>APN100,TCPIP*:=4539.94N/01417.67E-QTH'

Why did I write “senduser” in string? Because you can place remote objects on the map that’s not your own call sign – sticking to the protocol rules, you need to change the code to show the correct path if you want to place objects with not your own sign. The only drawback with Xastir in this example is, it overrides your permanent timestamp (111111z). More info about sending objects with not your sign is in my previous post. Why is it so “cool” to collect and send data from another stations to RF? Let’s say you want to transmit critical data to offline users like repeater offset or echolink node number. You can also transmit weather data from other non RF stations to RF. So for example if I’m walking in the forest with my handheld APRS capable station I can receive weather or repeater data directly on my handheld without having the internet.


I’m collecting data from this weather station: https://www.hobolink.com/p/d0a7b4f0dbc44b973b0a5cce75a0521d and sending it to RF, including telemetry.


Another example is sending repeater and echolink data to RF:

You see that purple lines? That means the object was put on the map via my station (S55MA-10).

You can also see the path at the bottom of a white rectangle “[APN101 via WIDE2-1,qAR,S55MA-10]”

Note that this is actually faking an object, it looks like the transmitter is at the object and it’s

being igated by my station, but it’s actually my station doing the transmissions. This is a good example  how to not

stick to the protocol rules 😉 I was unable to do it with xastir_udp_client binary, it rewrites it’s path if you want to send another

object. There is also a “bug” or a feature, I’m not sure yet, that xastir_udp_client inserts additional } in the string. I don’t know

why is that, but it’s unwanted.

If you want to show that remote object is put on the map by your station, you need to change the path as I mentioned above,

stick to the protocol, but I don’t think that’s possible with xastir_udp_client. I neglected this and I’m going to update my scripts

if I find the correct solution.

Example script I’m using to send Echolink data to RF:


#Transmit objects via Xastir to RF.

#Define login info

#Define object user info

#Define xastir server

#Define station location (Echolink Postojna, Pecna Reber)

#Define data
comment="Echolink Postojna 438.825Mhz -7.6M T123 Node:609569"

#Send object to RF
/usr/bin/xastir_udp_client $server $port $user $password -to_rf "$data"

#printf "$data\n"

Example of my hobolink weather collector script. Hobolink to APRS. It’s similar to the script in my previous post so I won’t go into details.


#Read data from HOBOlink station and send it to APRS network via Xastir. Server ports option has to be enabled on Xastir.

#Create a temporary RAM disk (we don't want to write on a SD card too often).
#You need to run this script as sudo (root) or create a temporary ramdisk at boot as root
#and run this script as a normal user.
if [ ! -d "/mnt/ramdisk/" ]; then
mkdir -p /mnt/ramdisk; mount -t tmpfs tmpfs /mnt/ramdisk -o size=10m

#Check if file exist
if [ ! -f "/mnt/ramdisk/sequence_numberjavornik.txt" ]; then
touch /mnt/ramdisk/sequence_numberjavornik.txt

#Read sequence number
read num < /mnt/ramdisk/sequence_numberjavornik.txt
num=$((num + 1))
if (( num == 1000 )); then

#Error log file

#Define login info

#Insert the same as user. Insert other user sign if you want to put another station not owned by you on the map. Be aware that telemetry
#requires 9 char long callsign so you need to add whitespaces after the callsign and telemetry, for example:
#t2="$usersend>APN002,WIDE2-1::"$usersend" :PARM.Solar Radiation,Battery" #5 whitespaces between "$usersend" and :PARM because
#S51Y is only 4 char long.

#Define xastir server

#Define station location (Veliki Javornik, Postojna)
lon=01417.72E_ #_ is a symbol for WX station

#Download weather data
curl -s https://www.hobolink.com/p/d0a7b4f0dbc44b973b0a5cce75a0521d | grep nobr | awk -F\> '{print $6}' | sed 's/<\/nobr//g' > "$file"
curl -s https://www.hobolink.com/p/d0a7b4f0dbc44b973b0a5cce75a0521d | grep "Wind Direction" -A 1 | grep "latest-conditions-info-reading" | awk -F\> '{print $5}' | awk '{print $2}' | grep -o '[0-9]\+' > "$file1"

if [ -s "$file" ] #If downloaded file is not empty, continue, else quit

#Date in UTC
zuludate="$(date -u +%d%H%M)"

#APRS needs temperature in F, data is fetched in degress C so we need to convert it.
tempC="$(sed -n -e 1p "$file")"
tempF="$(echo "((9/5) * $tempC) + 32" | bc -l | awk -F. '{print $1}')"

#Relative humidity
rh="$(sed -n -e 2p "$file" | awk -F. '{print $1}')"

#APRS need windspeed in mph, data is fetched in meters per second so we need to convert it.
windspeedms="$(sed -n -e 4p "$file")"
windspeedmph="$(echo "(($windspeedms * 3.6) / 1.609344)" | bc -l | awk -F. '{print $1}')"
gustsms="$(sed -n -e 5p "$file")"
gustsmph="$(echo "(($gustsms * 3.6) / 1.609344)" | bc -l | awk -F. '{print $1}')"

rain1h="$(sed -n -e 7p "$file" | awk -F. '{print $1}')"
winddirection="$(cat "$file1")"
solarradiation="$(sed -n -e 6p "$file")" #For telemetry only
batteryvoltage="$(sed -n -e 8p "$file" | tr -d '.' | head -c 3)" #For telemetry only

#Station comment
aprscomment="Veliki Javornik 1268m asl"

#Xastir weather variable with padding zeros for correct APRS format
printf -v xastirwx "%03d/%03dg%03dt%03dr%03dh%02d%s" "$winddirection" "$windspeedmph" "$gustsmph" "$tempF" "$rain1h" "$rh" "$aprscomment"

#Xastir user and WIDE path data

printf -v t1 "%s>APN002,WIDE2-1:T#%03d,%03d,%03d,000,000,000,00000000" "$usersend" "$num" "$solarradiation" "$batteryvoltage"
t2="$usersend>APN002,WIDE2-1::$usersend :PARM.Solar Radiation,Battery"
t3="$usersend>APN002,WIDE2-1::$usersend :UNIT.W/m2,Volts"

#Add coefficient in EQNS field to convert real data.
t4="$usersend>APN002,WIDE2-1::$usersend :EQNS.0,1,0,0,0.01,0,0,0,0,0,0.0,0,0,0,0"
t5="$usersend>APN002,WIDE2-1::$usersend :BITS.00000000,Weather station Veliki Javornik Postojna"

#Send data to Xastir
/usr/bin/xastir_udp_client $server $port $user $password -to_rf "$xastirpath$xastirwx"

#Send telemetry data to Xastir
#Send PARAMS, UNITS, EQNS and BITS every 2 hours.
#Check if file exist
if [ ! -f "/mnt/ramdisk/datejavornik.txt" ]; then
echo 0 > /mnt/ramdisk/datejavornik.txt

#calculate time difference
read olddate < /mnt/ramdisk/datejavornik.txt
date="$(date +%s)"
diff="$(echo "$date - $olddate" | bc)"

if [ "$diff" -gt 7200 ]; then
/usr/bin/xastir_udp_client $server $port $user $password -to_rf "$t1"
/usr/bin/xastir_udp_client $server $port $user $password -to_rf "$t2"
/usr/bin/xastir_udp_client $server $port $user $password -to_rf "$t3"
/usr/bin/xastir_udp_client $server $port $user $password -to_rf "$t4"
/usr/bin/xastir_udp_client $server $port $user $password -to_rf "$t5"
echo "$date" > /mnt/ramdisk/datejavornik.txt
/usr/bin/xastir_udp_client $server $port $user $password -to_rf "$t1"

#Delete old data
rm -f /mnt/ramdisk/wxdata.txt /mnt/ramdisk/wxdata1.txt

#Write sequence number
echo "$num" > /mnt/ramdisk/sequence_numberjavornik.txt

echo ["$(date -u)"] Error downloading data >> "$error"

Link to the Hobolink to APRS script: https://pastebin.com/v6cQkm54

This is to internet only, not to RF. You need TNC software and a radio station to send it via RF.

We need ncat which is a part of nmap and bc.

sudo apt-get install nmap bc

First, some usefull tools:
APRS coordinate converter: http://digined.pe1mew.nl/?How_to:Convert_coordinates
APRS passcode generator: http://apps.magicbug.co.uk/passcode/

There are more APRS IS servers available to connect to, I decided to use poland.aprs2.net, the generic one is rotate.aprs.net.
Default port is 14580.

The protocol goes like this (more info http://www.aprs-is.net/Connecting.aspx):
– Authenticate with APRS-IS server
– Send data

So the data you are sending is:
– user yourHAMSIGN pass passcode
– objectdata

Bash one line command to send data:

printf "%s\n" "user yourHAMSIGN pass passcode" "yourHAMSIGN>APN100,TCPIP*:=latitude/longtitude-comment" | ncat poland.aprs2.net 14580


printf "%s\n" "user S55MA-13 pass 23558" "S55MA-13>APN100,TCPIP*:=4246.29N/01412.79E_247-QTH" | ncat poland.aprs2.net 14580

This should place S55MA-13 on a map with a house icon.
Look into aprs.fi raw data for your ham sign (https://aprs.fi/?c=raw&call=yourHAMSIGN) to find any formatting errors. It should look like this:

2017-07-27 16:18:32 CEST: S55MA-13>APN100,TCPIP*,qAC,T2POLAND:=4246.29N/01412.79E_247-QTH

Important: With this commands you can place (fake) or “steal” an objects that are not your own stations by placing another station sign in the second yourHAMSIGN field. This will work, but it will look like the object is coming via another station, igated by your station. That’s ok, the object will be placed correctly on the map, but for the sake of following correct protocol procedures, you need to change a string a little bit so the other station object will show correct path of where it’s coming from.

When placing another object (not your own) on the map, you need to add the timestamp to the string. If the object is permanent, timestamp should be “111111z” (http://www.aprs.org/info/object-perm.txt)


printf "%s\n" "user yourHAMSIGN pass passcode" "yourHAMSIGN>APN100,TCPIP*:;ANOTHERSTATION_CALLSIGN *111111zlatitude/longtitude-comment" | ncat poland.aprs2.net 14580
printf "%s\n" "user S55MA-10 pass 22358" "S55MA-10>APN100,WIDE2-1*:;S55MA-14 *111111z4139.94N/01217.67E-test" | ncat --send-only poland.aprs2.net 14580

Note: ANOTHERSTATION_CALLSIGN is by protocol defined as 9 characters long and you need to add whitespaces between ANOTHERSTATION_CALLSIGN and *111111z
Example: S51Y has four characters only so you need to add 5 whitespaces between ANOTHERSTATION_CALLSIGN and *111111z
The string would look like:

printf "%s\n" "user S55MA-10 pass 22358" "S55MA-10>APN100,WIDE2-1*:;S51Y     *111111z4139.94N/01217.67E-test" | ncat --send-only poland.aprs2.net 14580

If you don’t want to use one line command, you can create a simple script (for placing your own station):


#Define login info

#Define object user info

#Define APRS-IS server

#Define station location

#Define data

#Send data to the server
printf "%s\n" "user $user pass $password" "${senduser}>APN100,TCPIP*:=${lat}/${lon}-${comment}" | ncat poland.aprs2.net 14580

NOTE: Don’t send data to often. For objects it’s sufficient to send it every few hours. Don’t spam the network.

You can send telemetry the same way but it’s a little bit trickier because APRS protocol has coeficients for displaying the correct values.
For example let’s say we’ll read a raspberryPI CPU temperature and send it to APRS-IS network.

#Define login info

#Define object user info

#Define APRS-IS server

#Define station location

#Define data

#Authentication variable
aprsauth="user $user pass $password"

#Read raspberry-pi CPU temperature
tempraw=/opt/vc/bin/vcgencmd measure_temp #Read pi temperature

#Filter result to numbers only 
tempfloat="$(echo "$tempraw" | awk -F= '{print $2}' | awk -F\' '{print $1}')"

#Aprs telemetry protocol accepts 3 whole (int) 
#numbers only so we need
#to convert the result (tempfloat) into 3 whole 
#numbers. So if tempraw=40.3, it will
#convert it to 403. If tempraw=8.5, it will convert 
#it to 85 and we'll add the leading zero
#to create 3 number format 085 later bellow.
temp="$(echo "$tempfloat * 10" | bc | awk -F. '{print $1}')"

#Project comment
projectcomment=Rpi temperature

#Generate telemetry strings
#Read data and put it into variable
#%s means string
#%03d means prepend up to 3 zeroes, so if the value is 8, 
#you'll get 008, if the value is 80, you'll get 080 etc.
#This needs to be done or APRS will deny the packets as invalid. 
#The other things is, everytime you send the telemetry,
#a sequence number has to change, it's defined as T# in a APRS protocol. 
#We will automate this next.

#Check if file exist
if [ ! -f "/tmp/sequence_number.txt" ]; then
 touch /tmp/sequence_number.txt

#Read sequence number. Everytime the scripts runs, the number will 
#rise by 1 until it comes
#to 1000 and then returns back to 0. Everytime the script
#will run, a sequence number will change.
read num < /tmp/sequence_number.txt
num=$((num + 1))
if (( num == 1000 )); then

#Finally, we can start assembling the data. 
#$senduser goes to %s, $num goes to first %03d and $temp goes to 
#the last %03d in the string. 
printf -v t1 "%s>APN001,TCPIP*:T#%03d,%03d,000,000,000,000,00000000" "$senduser" "$num" "$temp"

#Define telemetry parameters
t2="$user>APN001,TCPIP*::$senduser :PARM.CPU Temp"

#Define telemetry units
t3="$user>APN001,TCPIP*::$senduser :UNIT.Deg.C"

#Add telemetry coefficient so the APRS protocol can convert your raw values
#into real value.
#We get the value in 3 whole numbers and we need to define coefficient so 
#the APRS protocol
#will know how to display the value. We add 0.1 to the second field, means
#if the value is 452, the temperature will be displayed as 45.2
t4="$user>APN001,TCPIP*::$senduser :EQNS.0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0"

#Send bits and project comment
t5="$user>APN001,TCPIP*::$senduser :BITS.00000000,$projectcomment"

#Another tricky part is, $senduser total lenght has to be 9 characters. 
#For example
#If my $senduser=S55MA-10 means it's only 8 characters long and we need 
#to add 1 space to it. S55MA-10 :PARM
#If my $senduser=S55MA means it's only 5 characters long and we need to 
#add 4 spaces to it S55MA    :PARM
#The same goes for UNIT, EQNS and BITS

#Send data to the server
#For telemetry to work we need to have an object before, from previous script.
#We'll only send an object and telemetry non value data every hour so we don't 
#spam the network.
#We need to compare dates to see if 1 hour is past.

#Check if file exist
if [ ! -f "/tmp/date.txt" ]; then
   echo 0 > /tmp/date.txt

#calculate time difference
read olddate < /tmp/date.txt
date="$(date +%s)"
diff="$(echo "$date - $olddate" | bc)"

#If 3600 is past, execute the first command, else other
if [ "$diff" -gt 3600 ]; then
   printf "%s\n" "$aprsauth" "$data" | ncat --send-only $server $port #this is your QTH object from the first script
   printf "%s\n" "$aprsauth" "$t1" "$t2" "$t3" "$t4" "$t5" | ncat --send-only $server $port 
   echo "$date" > /tmp/date.txt
   printf "%s\n" "$aprsauth" "$t1" | ncat --send-only $server $port
#Write the last sequence number.
echo "$num" > /tmp/sequence_number.txt

Telemetry should form on aprs.fi, for example: https://aprs.fi/telemetry/a/S55MA-10
Note, formatting in script might be broken due html formatting, here is a raw script: https://pastebin.com/24CaMXLT

I’ve bought an affordable geiger counter board that fits on a Raspberry pi zero (https://www.tindie.com/products/granzscientific/raspberry-pi-zero-iot-geiger-counter/).  I’ve modified original software and added some scripts to send radiation readings to the APRS network. I’ve also added a Munin plugin. Go to the github project site for instructions: https://github.com/s55ma/granzscientific-geiger-counter-APRS

Instal libav-tools

sudo apt-get update
sudo apt-get install libav-tools

Install youtube-dl

sudo curl -L https://yt-dl.org/downloads/latest/youtube-dl -o /usr/local/bin/youtube-dl
sudo chmod a+rx /usr/local/bin/youtube-dl

Download mp3

youtube-dl --extract-audio --audio-format mp3 --audio-quality 0 -o '%(title)s.%(ext)s' https://www.youtube.com/watch?v=ID

–audio-quality 0 means the best quality available
-o ‘%(title)s.%(ext)s’ will remove the ID from the filename

Download the file from a web browser (optional if you don’t have FTP, SFTP, are on a Windows bashless box)

For python bellow the version 3

python -m SimpleHTTPServer

For python 3 or higher

python3 -m http.server

Now just browse to http://your-server-IP:8000 and download the file

My SDRsharp was often crashing so I wanted to use rtl_fm CLI (command line interace) to receive NOAA images. I found this post http://www.rtl-sdr.com/an-alternative-noaa-weather-satellite-tutorial-using-rtl_fm-and-wxtoimg/ which was poorly and incompletely written but it gave me a jump start.

This was tested on a 64bit Windows 7. I assume you have Zadig drivers working already (http://www.rtl-sdr.com/tag/zadig/)

The software:


WxToImg will send commands to MCE controller which will start rtl_fm and sox applications which will pipe the audio to virtual audio cable. WxToImg will listen to virtual audio interface and capture the data.

TL;DR: WxToImg -> MCE -> rtl_fm+sox -> Virtual audio cable -> WxToImg

NOTICE: This tutorial does not include doppler shift correction but with NOAA it’s not that important.

1. Install Virtual Audio Cable

The following step is optional but recommended. We’ll need it later for the sox.

Go to playback devices (right click on the speaker in the taskbar).

Right click on your VB-Audio Virtual Cable, properties and rename it to Vcable.


2. Download and install sox (https://sourceforge.net/projects/sox/files/sox)

If you get missing DLL files when runing sox, install Microsoft Visual C++ 2010 Service pack 1 Redistributable package MFC security update.

3. Download rtl_fm software and unzip it (http://osmocom.org/attachments/download/2242/RelWithDebInfo.zip)

4. Download and install Virtual Serial port driver (https://www.eltima.com/download/vspd.exe)

Open the app, select two COM ports and click add pair.

You end up with COM2 and COM3 in the left menu. You can choose different ports, but you need to reflect that in WxToImg and later in MCE controller settings.

5. Download and install MCE control (https://github.com/tig/mcec/releases/download/1.9.0/MCEController.Setup.exe)

Open it and go to Settings -> Serial server

Tick Enable serial server

Port: COM3

Baud rate: 2400

6. Install WxToImg and register it with this data:

Name: Carl Rakotondrainibe
Email: carl@Rakotondrainibe.com

Setup all your preferences and go to Options -> Recording options

Under Common recording options:

soundcard: Vcable

receiver type: select Baykal (unregistered version is missing this option)

receiver port: COM2

receiver baud rate: 2400

Save and exit

MCE controller and WxToImg should now be connected together with COM2 – COM3 pair we created earlier.

7. Create a file for MCE controller commands.

MCE will recognize the commands sent by WxToImg and start rtl_fm and sox.

Find your MCEcontrol.exe location (usually in “C:\Program Files (x86)\Kindel Systems\MCE Controller\”)

Go to that folder and create a new file named MCEControl.commands

Add this text to it and save it:

<?xml version="1.0" encoding="utf-8"?>
<MCEController xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Commands xmlns="http://www.kindel.com/products/mcecontroller">
<!-- Place command definitions here -->
StartProcess Commands
File: The full path to the executable you want to start.
<StartProcess Cmd="RF01376200" File="F:\satreceive\noaa15.bat"/>
<StartProcess Cmd="RF01379125" File="F:\satreceive\noaa18.bat"/>
<StartProcess Cmd="RF01371000" File="F:\satreceive\noaa19.bat"/>
<StartProcess Cmd="RF01375000" File="F:\satreceive\noaa19.bat"/>
<StartProcess Cmd="MUA" File="F:\satreceive\killrtl.bat"/>

IMPORTANT: Change the path of the bat files to your own location. Bat files are included in the next steps.

Just a brief explanation of the MCEcontrol.commands file:


= Command sent by WxToImg, a frequency of the NOAA15 Satellite and must be linked with noaa15.bat

Cmd="MUA" File="F:\satreceive\killrtl.bat"

= Command sent by WxToImg to start and stop recording

8. Bat files (make sure to edit them to match your sox, rtl_fm locations and rtl_fm device settings like gain and ppm correction):


REM start minimized
if not DEFINED IS_MINIMIZED set IS_MINIMIZED=1 && start "" /min "%~dpnx0" %* && exit

@echo off
REM setup paths, mind the end backslash

SET sox_path="F:\Program Files (x86)"\sox-14-4-2\
SET rtl_fm_path=F:\rtl-sdr-release\x64\
REM set RTL device number, run rtl_test, usually it's device 0 with only 1 dongle connected
REM set gain
REM set ppm correction
REM set your audio device, usually virtual audio cable

SET device=0
SET gain=49.6
SET ppm_correction=1
SET audiodevice=Vcable

REM Run sox and rtl_fm
%sox_path%sox.exe -r 48000 -t raw -es -b 16 -c 1 "|%rtl_fm_path%rtl_fm.exe -d %device% -f 137.62M -s 48000 -g %gain% -p %ppm_correction% -F 9 -A fast -E DC" -t waveaudio %audiodevice%



REM start minimized
if not DEFINED IS_MINIMIZED set IS_MINIMIZED=1 && start "" /min "%~dpnx0" %* && exit

@echo off
REM setup paths, mind the end backslash

SET sox_path="F:\Program Files (x86)"\sox-14-4-2\
SET rtl_fm_path=F:\rtl-sdr-release\x64\

REM set RTL device number, run rtl_test, usually it's device 0 with only 1 dongle connected
REM set gain
REM set ppm correction
REM set your audio device, usually virtual audio cable

SET device=0
SET gain=49.6
SET ppm_correction=1
SET audiodevice=Vcable

REM Run sox and rtl_fm
%sox_path%sox.exe -r 48000 -t raw -es -b 16 -c 1 "|%rtl_fm_path%rtl_fm.exe -d %device% -f 137.9125M -s 48000 -g %gain% -p %ppm_correction% -F 9 -A fast -E DC" -t waveaudio %audiodevice%



REM start minimized
if not DEFINED IS_MINIMIZED set IS_MINIMIZED=1 && start "" /min "%~dpnx0" %* && exit

@echo off
REM setup paths, mind the end backslash

SET sox_path="F:\Program Files (x86)"\sox-14-4-2\
SET rtl_fm_path=F:\rtl-sdr-release\x64\

REM set RTL device number, run rtl_test, usually it's device 0 with only 1 dongle connected
REM set gain
REM set ppm correction
REM set your audio device, usually virtual audio cable

SET device=0
SET gain=49.6
SET ppm_correction=1
SET audiodevice=Vcable

REM Run sox and rtl_fm
%sox_path%sox.exe -r 48000 -t raw -es -b 16 -c 1 "|%rtl_fm_path%rtl_fm.exe -d %device% -f 137.1M -s 48000 -g %gain% -p %ppm_correction% -F 9 -A fast -E DC" -t waveaudio %audiodevice%



@echo off
taskkill /IM rtl_fm.exe /F

Everything should be set now. You can test it by going to WxToImg File -> Manual Test

You should see some data in the MCE Controller, bat file executed and WxToImg recording:



Main apps needed:
– WxToImg
– Predict
– Rtl-SDR
– Sox
– Dropbox bash uploader
– Some essential packages
– A script to link it all together, provided by a reddit user the2belo. Check out the sources at the bottom of this post.

apt-get update
apt-get install curl git-core git cmake at predict sox libusb-1.0-0-devx-fmt-all build-essential libasound-dev libpulse-dev -y
cat <<EOF >no-rtl.conf
blacklist dvb_usb_rtl28xxu
blacklist rtl2832
blacklist rtl2830
mv no-rtl.conf /etc/modprobe.d/
git clone git://git.osmocom.org/rtl-sdr.gi
cd rtl-sdr/
mkdir build
cd build
make install
cd ~
cp ./rtl-sdr/rtl-sdr.rules /etc/udev/rules.d/

Output should look similar to this:
root@rpi:~/wxsat# rtl_test
Found 1 device(s):
0: Realtek, RTL2838UHIDIR, SN: 00000001

Using device 0: Generic RTL2832U OEM
Found Rafael Micro R820T tuner
Supported gain values (29): 0.0 0.9 1.4 2.7 3.7 7.7 8.7 12.5 14.4 15.7 16.6 19.7 20.7 22.9 25.4 28.0 29.7 32.8 33.8 36.4 37.2 38.6 40.2 42.1 43.4 43.9 44.5 48.0 49.6
[R82XX] PLL not locked!
Sampling at 2048000 S/s.

Info: This tool will continuously read from the device, and report if
samples get lost. If you observe no further output, everything is fine.

Reading samples in async mode…

wget http://www.wxtoimg.com/beta/wxtoimg-armhf-2.11.2-beta.deb
dpkg -i wxtoimg-armhf-2.11.2-beta.deb



Setup your QTH.

cd ~
mkdir wxsat
wget https://raw.githubusercontent.com/the2belo/wxsat-scheduler/master/noaa-scheduler.sh
chmod +x noaa-scheduler.sh
mkdir images recordings

Edit noaa-scheduler.sh to reflect your location.

Change -L parameter
/usr/local/bin/wxmap -T “NOAA ${bird}” -H ~/wxsat/weather.txt -L “35.47/136.76/20” -p0 -o “\$mapdate” ~/wxsat/noaa${bird}map.png

Format is: (lat/lon/alt meters)
Use this site to get lat and lon for your location http://www.latlong.net/lat-long-dms.html

Edit noaa-scheduler.sh for gain and SDR dongle ppm correction:

timeout $rectime /usr/local/bin/rtl_fm -d 0 -f ${freq}M -s 48000 -g 44.5 -p 1 -F 9 -A fast -E DC ~/wxsat/recordings/NOAA${bird}-\$recdate.raw

-g = gain

-p = ppm

Play with gain settings to get the best results.

You also need to add the timezone for the script, add

export TZ='UTC date'

in a new line after #!/bin/bash and after cat << EOF > ~/wxsat/noaa${bird}.at

This is how it looks like in a final version: https://pastebin.com/3uWcp9wi

Script generates MCIR and HCVT images by default, you can change this options. I also added -c option to crop telemetry from the sides of the pictures.

/usr/local/bin/wxtoimg -c -e MCIR -m ~/wxsat/noaa${bird}map.png ~/wxsat/recordings/NOAA${bird}-\$recdate.wav ~/wxsat/images/NOAA${bird}-MCIR-\$recdate.png

mkdir Dropbox-Uploader
cd Dropbox-Uploader
curl "https://raw.githubusercontent.com/andreafabrizi/Dropbox-Uploader/master/dropbox_uploader.sh" -o dropbox_uploader.sh
chmod +x dropbox_uploader.sh

Follow the instructions.

Automate the proces. Commands bellow will be run every 12 hours.

crontab -e

00 */12 * * * atq | awk ‘{print $1}’ | sort -n | xargs atrm
01 */12 * * * ~/wxsat/noaa-scheduler.sh 15 137.620
02 */12 * * * ~/wxsat/noaa-scheduler.sh 18 137.9125
03 */12 * * * ~/wxsat/noaa-scheduler.sh 19 137.100
00 */12 * * * wget -qr https://www.celestrak.com/NORAD/elements/weather.txt -O ~/wxsat/weather.txt

Start the jobs immediately:

wget -qr https://www.celestrak.com/NORAD/elements/weather.txt -O ~/wxsat/weather.txt
~/wxsat/noaa-scheduler.sh 15 137.620
~/wxsat/noaa-scheduler.sh 18 137.9125
~/wxsat/noaa-scheduler.sh 19 137.100

If you are going to change the file noaa-scheduler.sh, you need to kill the existing jobs and restart them. Kill the jobs with:

atq | awk '{print $1}' | sort -n | xargs atrm


There is another piece of software to automatically receive NOAA and Meteor satellites, I have yet to check it out.

Link: https://github.com/cyber-atomus/autowx

EDIT: I have experienced some lag with webm streaming, scroll down for swf configuration.

Live stream camera (Foscam in my case) over the internet cheat sheet:

apt-get install software-properties-common
add-apt-repository ppa:mc3man/trusty-media
apt-get update
apt-get install ffmpeg

Create ffserver.conf file

nano /etc/ffserver.conf
HTTPPort 8090 # Port to bind the server to
MaxHTTPConnections 2000
MaxClients 15
MaxBandwidth 50000 # Maximum bandwidth per client
 # set this high enough to exceed stream bitrate
CustomLog -

<Feed feed1.ffm>
 File /tmp/feed1.ffm
 FileMaxSize 1g
 ACL allow localhost
 ACL allow

<Stream live.webm>
 Format webm
 Feed feed1.ffm

 VideoCodec libvpx
 VideoSize 640x480
 VideoFrameRate 30
 VideoBitRate 512
 VideoBufferSize 512
 AVOptionVideo flags +global_header


<Stream status.html> # Server status URL
 Format status
 # Only allow local people to get the status
 ACL allow localhost
 ACL allow

<Redirect index.html> # Just an URL redirect for index
 # Redirect index.html to the appropriate site
 URL live.webm/

Save, exit and start the services

ffserver > /dev/null 2>&1 &
ffmpeg -loglevel error -r 30 -i rtsp://user:pass@camera_IP:port/url http://LAN_IP_WHERE_FFSERVER_IS_RUNNING:8090/feed1.ffm /dev/null 2>/var/log/ffmpeg.log &

Visit http://LAN_IP_WHERE_FFSERVER_IS_RUNNING:8090/live.webm
For status go to http://LAN_IP_WHERE_FFSERVER_IS_RUNNING:8090/status.html

I have experienced some lag with the above configuration, here is the config for a swf streaming:

HTTPPort 8090 # Port to bind the server to
MaxHTTPConnections 2000
MaxClients 15
MaxBandwidth 50000 # Maximum bandwidth per client
 # set this high enough to exceed stream bitrate
CustomLog -

<Feed feed1.ffm>
 File /tmp/feed1.ffm
 FileMaxSize 1g
 ACL allow localhost
 ACL allow

<Stream live.ffm>
 Feed feed1.ffm

Format swf
VideoCodec flv
VideoFrameRate 30
VideoBufferSize 80000
VideoBitRate 100
# quality ranges - 1-31 (1 = best, 31 = worst)
VideoQMin 1
VideoQMax 5
VideoSize 1024x786
PreRoll 0


<Stream status.html> # Server status URL
 Format status
 # Only allow local people to get the status
 ACL allow localhost
 ACL allow

<Redirect index.html> # Just an URL redirect for index
 # Redirect index.html to the appropriate site
 URL live.ffm/

Save and restart ffserver and ffmpeg.

Edit your index.html file to play swf file in the browser:

<meta charset="UTF-8">
<link rel="icon" href="favicon.ico" type="image/x-icon" />
<link rel="shortcut icon" href="favicon.ico" type="image/x-icon" />
<title>Site title</title>
<script type="text/javascript" src="swfobject.js"></script>
 <script type="text/javascript">
 swfobject.registerObject("myFlashContent", "9.0.0", "expressInstall.swf");
 <script type="text/javascript">
 swfobject.embedSWF("http://FF_SERVER_IP:8090/live.ffm", "myContent", "1024", "786", "9.0.0");
<p align="top">Some optional text.</p>
 <div id="myContent">
 <p>Live stream</p>


Enable I2C with raspi-config and reboot

Check if the sensor is connected:

i2cdetect -y 1

The output should look like this:

root@rpikiosk:~# i2cdetect -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- 23 -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: 40 -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- 77

Our HTU21D device is detected as the number 40 in the line 40, so the address is 0X40.

import struct, array, time, io, fcntl

HTU21D_ADDR = 0x40

class i2c(object):
   def __init__(self, device, bus):

      self.fr = io.open("/dev/i2c-"+str(bus), "rb", buffering=0)
      self.fw = io.open("/dev/i2c-"+str(bus), "wb", buffering=0)

      # set device address

      fcntl.ioctl(self.fr, I2C_SLAVE, device)
      fcntl.ioctl(self.fw, I2C_SLAVE, device)

   def write(self, bytes):

   def read(self, bytes):
      return self.fr.read(bytes)

   def close(self):

class HTU21D(object):
   def __init__(self):
      self.dev = i2c(HTU21D_ADDR, 1) #HTU21D 0x40, bus 1
      self.dev.write(CMD_SOFT_RESET) #soft reset

   def ctemp(self, sensorTemp):
      tSensorTemp = sensorTemp / 65536.0
      return -46.85 + (175.72 * tSensorTemp)

   def chumid(self, sensorHumid):
      tSensorHumid = sensorHumid / 65536.0
      return -6.0 + (125.0 * tSensorHumid)

   def crc8check(self, value):
      # Ported from Sparkfun Arduino HTU21D Library: https://github.com/sparkfun/HTU21D_Breakout
      remainder = ( ( value[0] << 8 ) + value[1] ) << 8
      remainder |= value[2]
      # POLYNOMIAL = 0x0131 = x^8 + x^5 + x^4 + 1
      # divsor = 0x988000 is the 0x0131 polynomial shifted to farthest left of three bytes
      divsor = 0x988000
      for i in range(0, 16):
         if( remainder & 1 << (23 - i) ): remainder ^= divsor divsor = divsor >> 1
      if remainder == 0:
         return True
         return False
   def read_tmperature(self):
      self.dev.write(CMD_READ_TEMP_NOHOLD) #measure temp

      data = self.dev.read(3)
      buf = array.array('B', data)

      if self.crc8check(buf):
         temp = (buf[0] << 8 | buf [1]) & 0xFFFC
         return self.ctemp(temp)
         return -255
   def read_humidity(self):
      self.dev.write(CMD_READ_HUM_NOHOLD) #measure humidity

      data = self.dev.read(3)
      buf = array.array('B', data)
      if self.crc8check(buf):
         humid = (buf[0] << 8 | buf [1]) & 0xFFFC
         return self.chumid(humid)
         return -255
if __name__ == "__main__":
   obj = HTU21D()
   print "Temp:", obj.read_tmperature(), "C"
   print "Humid:", obj.read_humidity(), "% rH"

Run the script:

root@rpikiosk:~# ./HTU21DF.py
Temp: 22.9382104492 C
Humid: 45.0101318359 % rH

If you connect the ground wire to the wrong Rpi pin, you’ll get this error:

IOError: [Errno 5] Input/output error


Pi-Hole is a DNS based ad blocker. You can install it on a Debian based Linux distributions. To use it together with a PfSense firewall you need to change a few firewall settings:

  • Login to your PfSense and go to System -> General Setup:

In the first DNS Server field insert the IP of your Pi-Hole computer and save changes

  • Go to Services -> DNS Resolver:

Tick Enable DNS resolver

Tick Enable DNSSEC Support

Tick Enable Forwarding Mode (this will tell the PfSense to use a DNS server that we configured in a general settings in the previous step)

Save and that’s it.

This post will show you how to generate weather data from LA Crosse WS2300 series weather stations for submission to the APRS network.

APRS software I’m using is Xastir, but you can use this setup to push the weather data to other APRS applications also. Digipeater mode is already set up in Xastir. This post will not cover digipeating WX data.

1. Install packages

apt-get update

apt-get install  bc ncat unzip gcc build-essential

2. Download open2300 and compile fetch2300

Fetch2300 is part of open2300, it’s used to connect to the weather station and return the data. Credit goes to Kenneth Lavrsen (http://www.lavrsen.dk/foswiki/bin/view/Open2300)

wget -O "open2300-1-10.zip" https://sourceforge.net/projects/open2300/files/open2300/1.10/open2300-1.10.zip/download"

unzip open2300-1-10.zip

cd open2300-1.10

make fetch2300

cp fetch2300 /usr/local/bin/fetch2300

3. Edit  and rename open2300 config file named open2300-dist.conf or copy mine to /etc/open2300/open2300.conf

mkdir -p /etc/open2300

nano /etc/open2300/open2300.conf
SERIAL_DEVICE /dev/ttyUSB1 #/dev/ttyS0, /dev/ttyS1, COM1, COM2 etc
TIMEZONE 1 # Hours Relative to UTC. East is positive, west is negative
WIND_SPEED km/h # select MPH (miles/hour), m/s, or km/h
RAIN IN # Select mm or IN
PRESSURE hPa # Select hPa, mb or INHG

Note: make sure you select the correct SERIAL_DEVICE in the config. The station in my setup

is connected via USB to RS232 converter so the device is ttyUSB0. Also, don’t change the units, bash script will automatically convert them to be compatible with APRS network.

4. Copy bash script to desired location

mkdir -p /root/ws2300/

nano wxdata_v1.6.sh

#This script reads weather data via fetch program which is part of Open2300 suite written by Kenneth Lavrsen (http://www.
#It outputs the right data needed to feed Xastir for APRS weather reports. The scripts utilizes Ncat utility as server to
#serve the fetched output to Xastir.
#Fetched Data is pushed to Ncat server and then to Xastir. (Fetched data -> Ncat server -> Xastir)
#Ncat is part of Nmap, get it by installing Nmap.
#This script should work for LaCrosse weather stations, WS23xx series. Testing was done with WS2307.
#Written by S55MA and S56IUL, May 2016


#Start the Ncat server
chkncat=$(netstat -ant | grep $host:$port | grep -c LISTEN)
if [ "$chkncat" -ge "1" ]
echo "ncat already running, nothing to do"
nohup ncat -k -l --broker $host $port &>/dev/null &

#Start while loop
while true; do

echo "start `date`"

datetime=$(date '+%Y%m%d%H%M%S')
/usr/local/bin/fetch2300 $ws2300config > /tmp/wxdata-"$datetime".tmp
chkfile=$(ls -la $fetch_path | awk -F ' ' '{ print $5 }')

if [ "$chkfile" -le "43" ]
echo "No Data"
sleep 30
tempF=$(cat "$fetch_path" | grep To | grep -v 'min\|max\|DRtot\|TRtot' | awk '{print $2}')
temp1=$(echo "$tempF" | awk '{ printf ("%d\n",$1 + 0.5)}')
if [ "$temp1" -ge "99" ] || [ "$temp1" -le "-99" ]
if [ "$temp1" -le "-1" ]
if [ "$temp1" -ge "-9" ]
temp2=$(echo "$temp1" | sed 's/[-]//g')
temp=$(echo -0"$temp2")
temp2=$(echo "$temp1" | sed 's/[-]//g')
temp=$(echo -"$temp1")
temp=$(echo 0"$temp1")

windspeed2=$(cat "$fetch_path" | grep -m1 WS | grep -v 'min\|max\|DRtot\|TRtot'| awk '{print $2/1.609344}' | awk '{ printf ("%d\n",$1 + 0.5)}')
if [ "$windspeed2" -le "9" ]
windspeed=$(echo 00"$windspeed2")
if [ "$windspeed2" -le "99" ]
windspeed=$(echo 0"$windspeed2")
windspeed=$(echo "$windspeed2")

winddirection2=$(cat "$fetch_path" | grep DIR0 | awk '{print $2}' | sed 's/\..*$//')
if [ "$winddirection2" -le "9" ]
winddirection=$(echo 00"$winddirection2")
if [ "$winddirection2" -le "99" ]
winddirection=$(echo 0"$winddirection2")
winddirection=$(echo "$winddirection2")

rain1h=$(cat "$fetch_path" | grep R1h | grep -v 'min\|max' | awk '{print $2}' | sed 's/[.]//g')
rain24h=$(cat "$fetch_path" | grep R24h | grep -v 'min\|max' | awk '{print $2}' | sed 's/[.]//g')

airpressureR=$(cat "$fetch_path" | grep RP | grep -v 'min\|max' | awk '{print $2}')
airpressure2=$(echo "scale=1;$airpressureR / 1" | bc | sed 's/[.]//g')
if [ "$airpressure2" -le "9999" ]
airpressure=$(echo 0"$airpressure2")
airpressure=$(echo "$airpressure2")

relhumidity=$(cat "$fetch_path" | grep RHo | grep -v 'min\|max' | awk '{print $2}' | sed 's/\..*$//')

#Combine variables to forge Xastir string
printf "%s\n" "$xastir" | ncat --send-only $host $port
echo "$xastir"
sleep 3

rm -f /tmp/wxdata-*.tmp

echo "stop `date`"
echo "-----------------------------------"



If there are some formatting mistakes, the script is also available on pastebin: http://pastebin.com/29q8epF8

5. Start the script


You should see output similar to that:

start Tue May 3 00:36:47 CEST 2016

Leave the script running, open another terminal and check if ncat is getting data:

ncat localhost 1234

Wait a few seconds and you should get the result similar to that:


6. Go to Xastir and add the WX interface

Interface -> Interface Control -> Add -> Networked WX

WX Host:

WX Port: 1234

Save and start the interface.


7. Go to Xastir, View -> Own Weather Data

You should see your own weather data from the station


8. Start the script at boot and run it in background

Open /etc/rc.local and add

screen -d -m /root/ws2300/wxdata_v1.6.sh

Save and exit

Note: make sure you set your own path of the script location


My 5V RaspberryPi fan was very loud and annoying, so I wanted a solution to regulate the fan depending on the Pi temperature.

To achieve that, I used a mosfet switch, pulled out from an old computer motherboard, followed by some easy bash scripting.

Almost any mosfet from an old computer motherboard should work.




You can use any not used GPIO to connect the gate pin, in the picture above it’s #23.


I connected the mosfet pin to GPIO 19 in my case. You can choose  your own.


sudo apt-get update
sudo apt-get install bc

Enable selected GPIO pin, In my case, GPIO 19

echo "19" > /sys/class/gpio/export
echo "out" > /sys/class/gpio/gpio19/direction

If you wan’t to select another GPIO and you already exported one GPIO, you need to unexport previous GPIO.

echo "19" > /sys/class/gpio/unexport

Bash script for controlling fan “speed”:

pico rpipwm.sh

Paste the script bellow, edit your GPIO pin and save:

#Poor man's PWM
#16.11.2015 by S55MA
#Quick and dirty script for controlling fan speed on RaspberryPI
#No rights reserved

#Define GPIO pin

while true; do

#Read temp
temp=$(cat /sys/class/thermal/thermal_zone0/temp | awk 'NR == 1 { print $1 / 1000}' | cut -c -4)

#If temperature is equal or lower than 39.99, the fan will stop spinning
if [[ $(bc <<< "$temp <= 39.99") == 1 ]] ;
$(echo "0" > /sys/class/gpio/gpio$pin/value)

#If temperature is between 40 and 42.99, the fan will start with 1 second burst and 1 second sleep
if [[ $(bc <<< "$temp >= 40 && $temp <= 42.99") == 1 ]] ;
$(echo "1" > /sys/class/gpio/gpio$pin/value; sleep 1; echo "0" > /sys/class/gpio/gpio$pin/value; sleep 1)

#If temperature is between 43 and 47.99, the fan will start with 1 second burst and 0.5 second sleep
if [[ $(bc <<< "$temp > 43 && $temp <= 47.99") == 1 ]] ;
$(echo "1" > /sys/class/gpio/gpio$pin/value; sleep 1; echo "0" > /sys/class/gpio/gpio$pin/value; sleep 0.5)

#If temperature is equal or higher than 48, the fan will start spinning constantly
if [[ $(bc <<< "$temp >= 48") == 1 ]] ;
$(echo "1" > /sys/class/gpio/gpio$pin/value)
sleep 0.1
chmod +x rpipwm.sh

Enable auto start at boot:

sudo cp rpipwm.sh /etc/init.d/rpipwm.sh
sudo chmod +x /etc/init.d/rpipwm.sh
sudo update-rc.d /etc/init.d/rpipwm.sh defaults

or you can use crontab:

crontab -e

Add and save:

@reboot /path/to/script/rpipwm.sh
sudo update-rc.d cron defaults

The rpisystat script will report the following system parameters to your LCD:

  • Internal IP
  • External IP
  • CPU temperature
  • GPU temperature
  • CPU usage
  • Memory usage
  • Free disk space
  • Incoming and outgoing network traffic

You need:

1. Breadboard with T-Cobbler (or connect the wires directly)
2. 16×2 LCD board
3. Adjustable resistor (potentiometer) for adjusting LCD contrast

1. Wiring (source: https://learn.adafruit.com/drive-a-16×2-lcd-directly-with-a-raspberry-pi/wiring):

Pin #1 of the LCD goes to ground (black wire)
Pin #2 of the LCD goes to +5V (red wire)
Pin #3 (Vo) connects to the middle of the potentiometer (orange wire)
Pin #4 (RS) connects to the Cobbler #25 (yellow wire)
Pin #5 (RW) goes to ground (black wire)
Pin #6 (EN) connects to Cobbler #24 (green wire)
Skip LCD Pins #7, #8, #9 and #10
Pin #11 (D4) connects to cobbler #23 (blue wire)
Pin #12 (D5) connects to Cobbler #17 (violet wire)
Pin #13 (D6) connects to Cobber #21 (gray wire)
Pin #14 (D7) connects to Cobber #22 (white wire)
Pin #15 (LED +) goes to +5V (red wire)
Pin #16 (LED -) goes to ground (black wire)


This schematics is for RaspberryPi 1 version, but you can connect to the same pins on RaspberryPi 2 (picture bellow).


2. Software

Download required packages:

sudo apt-get update
sudo apt-get install python-dev python-setuptools python-pip git
sudo easy_install -U distribute
sudo pip install rpi.gpio

Download my scripts:

sudo git clone git://github.com/s55ma/16-2-LCD-rpisystat.git
cd 16-2-LCD-rpisystat
sudo ./rpisystat.py

Make sure you edit rpisystat.py to match your GPIO pins (default is for the wiring above). Also edit rx.sh and tx.sh to match your network adapter.

Check the display in action: https://www.youtube.com/watch?v=5YkLTBd5-bw

Scripts: https://github.com/s55ma/16-2-LCD-rpisystat

References: https://learn.adafruit.com/drive-a-16×2-lcd-directly-with-a-raspberry-pi/wiring

In my previous post, I was writting about how to graph temperature and humidity from AM2302 sensor on RasberryPi. In addition, we will add dewpoint monitoring. We need two variables to calculate dewpoint, temperature and humidity. I took some already made scripts and combined them together to fit my needs. I wanted to calculate dewpoint completely with bash and bc, but since I’m to lazy, I just used the python script from this blog.

Get to the root shell (we don’t want to type sudo everytime):

sudo -s

Create python script (for dewpoint calculations):

pico /opt/dewpoint.py

Paste the code, save and exit (CTRL + C), Y, ENTER

import sys
import numpy as np

# approximation valid for
# 0 degC < T < 60 degC
# 1% < RH < 100%
# 0 degC < Td < 50 degC

# constants
a = 17.271
b = 237.7 # degC

# sys.argv[0] is program name

def dewpoint_approximation(T,RH):

    Td = (b * gamma(T,RH)) / (a - gamma(T,RH))

    return Td

def gamma(T,RH):

    g = (a * T / (b + T)) + np.log(RH/100.0)

    return g

Td = dewpoint_approximation(T,RH)
print Td

Make the script executable:

chmod +x /opt/dewpoint.py

Create plugin file:

pico /etc/munin/plugins/dewpoint

Paste the code, save and exit (CTRL + C), Y, ENTER


case $1 in
cat <<'EOM'
graph_title Dewpoint
graph_vlabel Celsius
graph_category AM2302
dewpoint.label Temperature
dewpoint.draw AREASTACK
dewpoint.colour 403075
exit 0;;

humidity=$(/opt/lol_dht22/loldht 7 | grep -i "humidity" | cut -d ' ' -f3)
temperature=$(/opt/lol_dht22/loldht 7 | grep -i "temperature" | cut -d ' ' -f7)

printf "dewpoint.value "
python /opt/dewpoint.py $temperature $humidity
chmod +x /etc/munin/plugins/dewpoint

Open munin-node file:

pico /etc/munin/plugin-conf.d/munin-node

Add the line at the end of the file, save and exit:


Restart services:

/etc/init.d/munin-node restart



  • RasberryPi 2
  • AM2302 humidity/temperature sensor
  • Some wires from old PCs to connect sensor with RaspberryPi


  • Raspbian OS
  • Nginx
  • Munin
  • WiringPi
  • Lol_dht22

1. Solder wires to the sensor like on the picture above, and connect them to the correct pins:

Pin 1 on the AM2302 to pin 1 (+3.3V) on the GPIO connector (labeled P1 on the raspi)
Pin 2 on the AM2302 to pin 7 (GPIO 4) on the GPIO connector
Pin 4 on the AM2302 to pin 9 (Ground) on the GPIO connector

For detailed instructions, check this blog up to step 4: https://hackaday.io/project/3766/instructions

All shell commands will be run as root, so I will not use sudo.

2. Install Nginx (web server)

apt-get update
apt-get install nginx php5-fpm

3. Install Munin

Muning is a monitoring tool for sysadmins. It creates graphs to monitor various parameters. We will configure munin to display AM2302 sensor in graphs.

apt-get install munin munin-node munin-plugins-extra

Edit munin configuration file:

pico /etc/munin/munin.conf
 use_node_name yes

4. Configure Nginx virtual host. Dynazoom will work with this config.

pico /etc/nginx/sites-enabled/default or pico /etc/nginx/sites-enabled/your.domain.com
server {
        listen 443 ssl;
        ssl_certificate /etc/nginx/ssl/your.domain.com.crt;
        ssl_certificate_key /etc/nginx/ssl/your.domain.com.key;
        server_name your.domain.com;
        root "/var/cache/munin/www/";
        auth_basic            "Private access";
        auth_basic_user_file  /etc/munin/munin_htpasswd;

        location ^~ /munin-cgi/munin-cgi-graph/ {
                fastcgi_split_path_info ^(/munin-cgi/munin-cgi-graph)(.*);
                fastcgi_param PATH_INFO $fastcgi_path_info;
                fastcgi_pass unix:/var/run/munin/spawn-fcgi-munin-graph.sock;
                include fastcgi_params;

        location /static/ {
                alias /etc/munin/static/;

5. Generate SSL cert

mkdir /etc/nginx/ssl
openssl req -subj '/CN=your.domain.com' -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/nginx/ssl/your.domain.com.key -out /etc/nginx/ssl/your.domain.com.crt

6. Generate website password

apt-get install apache2-utils
htpasswd -c /etc/munin/munin_htpasswd admin

You will be promted to enter a new password.

7. Add common modules to munin

cd /usr/share/munin/plugins
wget -O pisense_ https://raw.github.com/perception101/pisense/master/pisense_
chmod a+x pisense_
ln -s /usr/share/munin/plugins/pisense_ /etc/munin/plugins/pisense_temp
ln -s /usr/share/munin/plugins/pisense_ /etc/munin/plugins/pisense_clock
pico /etc/munin/plugin-conf.d/munin-node
user root

8. Configure AM2302 prerequisites

apt-get install git-core
cd /opt/
git clone git://git.drogon.net/wiringPi
cd wiringPi
cd /opt/
git clone https://github.com/technion/lol_dht22
cd lol_dht22

9. Create plugins for munin

pico /etc/munin/plugins/DHT22-humidity

case $1 in
 cat <<'EOM'
graph_title Relative humidity
graph_vlabel Percent
graph_category AM2302
humidity.label RH
humidity.draw AREASTACK
humidity.colour 3E9BFB
 exit 0;;

printf "humidity.value "
/opt/lol_dht22/loldht 7 | grep -i "humidity" | cut -d ' ' -f3
chmod +x /etc/munin/plugins/DHT22-humidity
pico /etc/munin/plugins/DHT22-temperature

case $1 in
 cat <<'EOM'
graph_title Temperature
graph_vlabel Celsius
graph_category AM2302
temperature.label Celsius
temperature.label Temperature
temperature.draw AREASTACK
temperature.colour 00FF00
 exit 0;;

printf "temperature.value "
/opt/lol_dht22/loldht 7 | grep -i "temperature" | cut -d ' ' -f7
chmod +x /etc/munin/plugins/DHT22-temperature
pico /etc/munin/plugin-conf.d/munin-node

Add this to the end of the file:

user root

10. Enable Dynazoom for graphs

apt-get install spawn-fcgi libcgi-fast-perl
pico /etc/init.d/munin-fastcgi
#! /bin/sh

# Provides: spawn-fcgi-munin-graph
# Required-Start: $all
# Required-Stop: $all
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Description: starts FastCGI for Munin-Graph
# --------------------------------------------------------------
# Munin-CGI-Graph Spawn-FCGI Startscript by Julien Schmidt
# eMail: munin-trac at julienschmidt.com
# www: http://www.julienschmidt.com
# --------------------------------------------------------------
# Install: 
# 1. Copy this file to /etc/init.d
# 2. Edit the variables below
# 3. run "update-rc.d spawn-fcgi-munin-graph defaults"
# --------------------------------------------------------------
# Special thanks for their help to:
# Frantisek Princ
# Jérôme Warnier
# --------------------------------------------------------------
# Last Update: 14. February 2013
# Please change the following variables:

DAEMON_OPTS="-s $SOCK_FILE -F $FCGI_WORKERS -U $SOCK_USER -u $FCGI_USER -g $FCGI_GROUP -P $PID_FILE -- /usr/lib/munin/cgi/munin-cgi-graph"

# --------------------------------------------------------------
# No edits necessary beyond this line
# --------------------------------------------------------------

if [ ! -x $DAEMON ]; then
 echo "File not found or is not executable: $DAEMON!"
 exit 0

status() {
 if [ ! -r $PID_FILE ]; then
 return 1
 for FCGI_PID in `cat $PID_FILE`; do 
 if [ -z "${FCGI_PID}" ]; then
 return 1
 FCGI_RUNNING=`ps -p ${FCGI_PID} | grep ${FCGI_PID}`
 if [ -z "${FCGI_RUNNING}" ]; then
 return 1
 return 0
start() {
 if status; then
 echo "FCGI is already running!"
 exit 1

stop () { 
 if ! status; then
 echo "No PID-file at $PID_FILE found or PID not valid. Maybe not running"
 exit 1
 # Kill processes
 for PID_RUNNING in `cat $PID_FILE`; do
 kill -9 $PID_RUNNING
 # Remove PID-file
 rm -f $PID_FILE
 # Remove Sock-File
 rm -f $SOCK_FILE

case "$1" in
 echo "Starting $NAME: "
 echo "... DONE"

 echo "Stopping $NAME: "
 echo "... DONE"

 echo "Stopping $NAME: "
 echo "Starting $NAME: "
 echo "... DONE"
 if status; then
 echo "FCGI is RUNNING"
 echo "Usage: $0 {start|stop|force-reload|restart|status}"
 exit 1

exit 0
chmod 755 /etc/init.d/munin-fastcgi
update-rc.d munin-fastcgi defaults
/etc/init.d/munin-fastcgi start

11. Restart daemons and visit your munin site


/etc/init.d/nginx restart

/etc/init.d/munin-node restart

Go to https://your.domain.com/munin/


Check how to add dewpoint graph on my next post.



This is duplicate of my original post: https://troubleshoot-coltpython.blogspot.com/2015/05/ubuntu-nginx-php5-fpm-mariadb-varnish.html

This is my cheat sheet for future reference in case I forget. It’s composed from various blogs around the interwebz and modified to suit my needs. I thought someone might find this useful.
I won’t go into much details about config definitions, it’s cheat sheet after all 🙂

We’ll setup Varnish caching engine with Nginx. Nginx will also serve as reverse proxy for SSL (https) requests.

My working environment:

Ubuntu 14.04.2 LTS | Trusty
Nginx 1.4.6
PHP 5.5.9
Varnish: 4.0.3-2
MariaDB: 5.5.43

Important config sections are highlighted. You need to change this.

Varnish will listen on port 80 and forward requests to backend (Nginx) listening on port 8080.

We are doing this as root:

sudo -s


1. Install Varnish


apt-get install apt-transport-https

curl https://repo.varnish-cache.org/GPG-key.txt | apt-key add -

echo "deb https://repo.varnish-cache.org/ubuntu/ trusty varnish-4.0" &amp;amp;amp;gt;&amp;amp;amp;gt; /etc/apt/sources.list.d/varnish-cache.list

apt-get update

apt-get install varnish


2. Install Nginx + Naxsi


echo "deb http://ppa.launchpad.net/nginx/stable/ubuntu $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/nginx-stable.list

apt-key adv --keyserver keyserver.ubuntu.com --recv-keys C300EE8C

apt-get update

apt-get install nginx

apt-get install nginx-naxsi

apt-get install libpcre3-dev libssl-dev


3. Install PHP5-FPM


apt-get install php5-fpm


4. Install MariaDB server


apt-get install mariadb-server


5. Configure Nginx (I also use CloudFlare in front)

Generate certificates for SSL sessions:

mkdir -p /etc/nginx/ssl

openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/nginx/ssl/domain.example.key -out /etc/nginx/ssl/domain.example.crt

Edit nginx.conf:

nano /etc/nginx/nginx.conf

user www-data;
worker_processes 4;
pid /run/nginx.pid;

events {
worker_connections 768;
multi_accept on;
use epoll;

http {

#Basic Settings

#Cloudflare real IP
set_real_ip_from 2400:cb00::/32;
set_real_ip_from 2606:4700::/32;
set_real_ip_from 2803:f800::/32;
set_real_ip_from 2405:b500::/32;
set_real_ip_from 2405:8100::/32;
real_ip_header CF-Connecting-IP;

#Varnish get real IP
real_ip_header X-Forwarded-For;

#TCP and buffer settings
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 20;
client_max_body_size 15m;
client_body_timeout 60;
client_header_timeout 60;
#client_body_buffer_size 1K;
#client_header_buffer_size 1k;
#large_client_header_buffers 4 8k;
send_timeout 60;
reset_timedout_connection on;
types_hash_max_size 2048;
server_tokens off;

include /etc/nginx/mime.types;
default_type application/octet-stream;

#Logging Settings

access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;

#Gzip Settings
gzip on;
gzip_disable “msie6”;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_min_length 512;
gzip_buffers 16 8k;
gzip_http_version 1.1;
#gzip_types text/css text/javascript text/xml text/plain text/x-component application/javascript application/x-javascript application/json application/xml application/rss+xml font/truetype application/x-font-ttf font/opentype application/vnd.ms-fontobject image/svg+xml;

#Uncomment it if you installed nginx-naxsi
include /etc/nginx/naxsi_core.rules;

#Virtual Host Configs
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;

nano /etc/nginx/sites-enabled/example.domain.conf

server {
server_name example.domain.com www.example.domain.com;
root “/home/example.domain/public_html”;

index index.php;
client_max_body_size 10m;

access_log /home/example.domain/_logs/access.log;
error_log /home/example.domain/_logs/error.log;

if ($http_user_agent ~* (Baiduspider|webalta|nikto|wkito|pikto|scan|acunetix|morfeus|webcollage|youdao) ) {
return 401;

if ($http_user_agent ~* (HTTrack|clshttp|archiver|loader|email|harvest|extract|grab|miner) ) {
return 401;

location ~ /(\.|wp-config.php|readme.html|license.txt) {
return 404;

# Add trailing slash to */wp-admin requests.
rewrite /wp-admin$ $scheme://$host$uri/ permanent;

location / {
include /etc/nginx/naxsi.rules;
try_files $uri $uri/ /index.php$uri?$args;

#Needed by Naxsi
location /RequestDenied {
return 403;

location ~ “^(.+\.php)($|/)” {
fastcgi_split_path_info ^(.+\.php)(.*)$;

fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_param SERVER_NAME $host;

if ($uri !~ “^/uploads/”) {
fastcgi_pass unix:/var/run/example.domain_fpm.sock; #You can use TCP connection instead or single sock for all sites. You can define this (pools) in /etc/php5/fpm/pool.d/ 
include fastcgi_params;

location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ {
expires max;
log_not_found off;
access_log off;

location ~* \.(html|htm)$ {
expires 30m;

location ~* /\.(ht|git|svn) {
deny all;

#Add compression
gzip on;
gzip_comp_level 2;
gzip_min_length 1000;
gzip_proxied expired no-cache no-store private auth;
gzip_types text/plain application/x-javascript text/xml text/css application/xml;


server {
listen 443 ssl;
server_name example.domain.com www.example.domain.com;
ssl_certificate /etc/nginx/ssl/example.domain.crt;
ssl_certificate_key /etc/nginx/ssl/example.domain.key;

location / {

## Pass the request on to Varnish.
location / {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-Port 443;
proxy_set_header Host $host;
proxy_redirect off;

6. Configure naxsi for Nginx (Optimized for WordPress):


nano /etc/nginx/naxsi.rules

# Sample rules file for default vhost.

DeniedUrl “/RequestDenied”;

## check rules
CheckRule “$SQL >= 8” BLOCK;
CheckRule “$RFI >= 8” BLOCK;
CheckRule “$TRAVERSAL >= 4” BLOCK;
CheckRule “$EVADE >= 4” BLOCK;
CheckRule “$XSS >= 8” BLOCK;

# WordPress naxsi rules

BasicRule wl:1000,1001,1005,1007,1010,1011,1013,1100,1200,1308,1309,1310,1311,1315 “mz:$HEADERS_VAR:cookie”;
# xmlrpc
BasicRule wl:1402 “mz:$HEADERS_VAR:content-type”;

### simple BODY (POST)
BasicRule wl:1001,1015,1009,1311,1310,1101,1016 “mz:$URL:/|$BODY_VAR:customized”;
# comments
BasicRule wl:1000,1010,1011,1013,1015,1200,1310,1311 “mz:$BODY_VAR:post_title”;
BasicRule wl:1000 “mz:$BODY_VAR:original_publish”;
BasicRule wl:1000 “mz:$BODY_VAR:save”;
BasicRule wl:1008,1010,1011,1013,1015 “mz:$BODY_VAR:sk2_my_js_payload”;
BasicRule wl:1001,1009,1005,1016,1100,1310 “mz:$BODY_VAR:url”;
BasicRule wl:1009,1100 “mz:$BODY_VAR:referredby”;
BasicRule wl:1009,1100 “mz:$BODY_VAR:_wp_original_http_referer”;
BasicRule wl:1000,1001,1005,1008,1007,1009,1010,1011,1013,1015,1016,1100,1200,1302,1303,1310,1311,1315,1400 “mz:$BODY_VAR:comment”;
BasicRule wl:1100 “mz:$BODY_VAR:redirect_to”;
BasicRule wl:1000,1009,1315 “mz:$BODY_VAR:_wp_http_referer”;
BasicRule wl:1000 “mz:$BODY_VAR:action”;
BasicRule wl:1001,1013 “mz:$BODY_VAR:blogname”;
BasicRule wl:1015,1013 “mz:$BODY_VAR:blogdescription”;
BasicRule wl:1015 “mz:$BODY_VAR:date_format_custom”;
BasicRule wl:1015 “mz:$BODY_VAR:date_format”;
BasicRule wl:1015 “mz:$BODY_VAR:tax_input%5bpost_tag%5d”;
BasicRule wl:1015 “mz:$BODY_VAR:tax_input[post_tag]”;
BasicRule wl:1100 “mz:$BODY_VAR:siteurl”;
BasicRule wl:1100 “mz:$BODY_VAR:home”;
BasicRule wl:1000,1015 “mz:$BODY_VAR:submit”;
# news content matches pretty much everything
BasicRule wl:0 “mz:$BODY_VAR:content”;
BasicRule wl:1000 “mz:$BODY_VAR:delete_option”;
BasicRule wl:1000 “mz:$BODY_VAR:prowl-msg-message”;
BasicRule wl:1100 “mz:$BODY_VAR:_url”;
BasicRule wl:1001,1009 “mz:$BODY_VAR:c2c_text_replace%5btext_to_replace%5d”;
BasicRule wl:1200 “mz:$BODY_VAR:ppn_post_note”;
BasicRule wl:1100 “mz:$BODY_VAR:author”;
BasicRule wl:1001,1015 “mz:$BODY_VAR:excerpt”;
BasicRule wl:1015 “mz:$BODY_VAR:catslist”;
BasicRule wl:1005,1008,1009,1010,1011,1015,1315 “mz:$BODY_VAR:cookie”;
BasicRule wl:1101 “mz:$BODY_VAR:googleplus”;
BasicRule wl:1007 “mz:$BODY_VAR:name”;
BasicRule wl:1007 “mz:$BODY_VAR:action”;
BasicRule wl:1100 “mz:$BODY_VAR:attachment%5burl%5d”;
BasicRule wl:1100 “mz:$BODY_VAR:attachment_url”;
BasicRule wl:1001,1009,1100,1302,1303,1310,1311 “mz:$BODY_VAR:html”;
BasicRule wl:1015 “mz:$BODY_VAR:title”;
BasicRule wl:1001,1009,1015 “mz:$BODY_VAR:recaptcha_challenge_field”;
BasicRule wl:1011 “mz:$BODY_VAR:pwd”;
BasicRule wl:1000 “mz:$BODY_VAR:excerpt”;

BasicRule wl:1000 “mz:$BODY_VAR:delete_option|NAME”;
BasicRule wl:1000 “mz:$BODY_VAR:from|NAME”;

### Simple ARGS (GET)
# WP login screen
BasicRule wl:1100 “mz:$ARGS_VAR:redirect_to”;
BasicRule wl:1000,1009 “mz:$ARGS_VAR:_wp_http_referer”;
BasicRule wl:1000 “mz:$ARGS_VAR:wp_http_referer”;
BasicRule wl:1000 “mz:$ARGS_VAR:action”;
BasicRule wl:1000 “mz:$ARGS_VAR:action2”;
# load and load[] GET variable
BasicRule wl:1000,1015 “mz:$ARGS_VAR:load”;
BasicRule wl:1000,1015 “mz:$ARGS_VAR:load[]”;
BasicRule wl:1015 “mz:$ARGS_VAR:q”;
BasicRule wl:1000,1015 “mz:$ARGS_VAR:load%5b%5d”;

### URL
BasicRule wl:1000 “mz:URL|$URL:/wp-admin/update-core.php”;
BasicRule wl:1000 “mz:URL|$URL:/wp-admin/update.php”;
BasicRule wl:1009,1100 “mz:$URL:/wp-admin/post.php|$BODY_VAR:_wp_http_referer”;
BasicRule wl:1016 “mz:$URL:/wp-admin/post.php|$BODY_VAR:metakeyselect”;
BasicRule wl:11 “mz:$URL:/xmlrpc.php|BODY”;
BasicRule wl:11 “mz:$URL:/wp-cron.php|BODY”;
BasicRule wl:2 “mz:$URL:/wp-admin/async-upload.php|BODY”;
BasicRule wl:1100 “mz:$URL:/wp-admin/post.php|$BODY_VAR:_wp_original_http_referer|NAME”;
BasicRule wl:1000 “mz:$URL:/wp-admin/post.php|$BODY_VAR:metakeyselect|NAME”;
BasicRule wl:1000 “mz:$URL:/wp-admin/user-edit.php|$BODY_VAR:from|NAME”;
BasicRule wl:1100 “mz:$URL:/wp-admin/admin-ajax.php|$BODY_VAR:attachment%5burl%5d|NAME”;
BasicRule wl:1100 “mz:$URL:/wp-admin/post.php|$BODY_VAR:attachment_url|NAME”;
BasicRule wl:1000 “mz:$URL:/wp-admin/plugins.php|$BODY_VAR:verify-delete|NAME”;
BasicRule wl:1310,1311 “mz:$URL:/wp-admin/post.php|$BODY_VAR:post_category[]|NAME”;
BasicRule wl:1311 “mz:$URL:/wp-admin/post.php|$BODY_VAR:post_category|NAME”;
BasicRule wl:1310,1311 “mz:$URL:/wp-admin/post.php|$BODY_VAR:tax_input[post_tag]|NAME”;
BasicRule wl:1310,1311 “mz:$URL:/wp-admin/post.php|$BODY_VAR:newtag[post_tag]|NAME”;
BasicRule wl:1310,1311 “mz:$URL:/wp-admin/users.php|$BODY_VAR:users[]|NAME”;
BasicRule wl:1310,1311 “mz:$URL:/wp-admin/load-scripts.php|$ARGS_VAR:load[]|NAME”;
BasicRule wl:1000 “mz:$URL:/wp-admin/users.php|$ARGS_VAR:delete_count|NAME”;
BasicRule wl:1000 “mz:$URL:/wp-admin/users.php|$ARGS_VAR:update|NAME”;

# plain WP site
BasicRule wl:1000 “mz:URL|$URL:/wp-admin/update-core.php”;
BasicRule wl:1000 “mz:URL|$URL:/wp-admin/update.php”;
BasicRule wl:1009,1100 “mz:$URL:/wp-admin/post.php|$BODY_VAR:_wp_http_referer”;
BasicRule wl:1016 “mz:$URL:/wp-admin/post.php|$BODY_VAR:metakeyselect”;
BasicRule wl:11 “mz:$URL:/xmlrpc.php|BODY”;
BasicRule wl:11 “mz:$URL:/wp-cron.php|BODY”;
BasicRule wl:1100 “mz:$URL:/wp-admin/post.php|$BODY_VAR:_wp_original_http_referer|NAME”;
BasicRule wl:1000 “mz:$URL:/wp-admin/post.php|$BODY_VAR:metakeyselect|NAME”;
BasicRule wl:1000 “mz:$URL:/wp-admin/user-edit.php|$BODY_VAR:from|NAME”;
BasicRule wl:1100 “mz:$URL:/wp-admin/admin-ajax.php|$BODY_VAR:attachment%5burl%5d|NAME”;
BasicRule wl:1310,1311 “mz:$URL:/wp-admin/admin-ajax.php|$BODY_VAR:data[wp-auth-check]|NAME”;
BasicRule wl:1310,1311 “mz:$URL:/wp-admin/admin-ajax.php|$BODY_VAR:data[wp-check-locked-posts][]|NAME”;
BasicRule wl:1310,1311 “mz:$URL:/wp-admin/update-core.php|$BODY_VAR:checked[]|NAME”;
BasicRule wl:1310,1311 “mz:$URL:/wp-admin/load-scripts.php|$ARGS_VAR:load[]|NAME”;
BasicRule wl:1000 “mz:$URL:/wp-admin/users.php|$ARGS_VAR:delete_count|NAME”;
BasicRule wl:1000 “mz:$URL:/wp-admin/users.php|$ARGS_VAR:update|NAME”;

### Plugins
#WP Minify
BasicRule wl:1015 “mz:$URL:/wp-content/plugins/bwp-minify/min/|$ARGS_VAR:f”;

7. Configure Varnish:


nano /etc/varnish/default

# Configuration file for varnish
# /etc/init.d/varnish expects the variables $DAEMON_OPTS, $NFILES and $MEMLOCK
# to be set from this shell script fragment.
# Note: If systemd is installed, this file is obsolete and ignored. You will
# need to copy /lib/systemd/system/varnish.service to /etc/systemd/system/ and
# edit that file.

# Should we start varnishd at boot? Set to “no” to disable.

# Maximum number of open files (for ulimit -n)

# Maximum locked memory size (for ulimit -l)
# Used for locking the shared memory log in memory. If you increase log size,
# you need to increase this number as well

# Default varnish instance name is the local nodename. Can be overridden with
# the -n switch, to have more instances on a single server.
# INSTANCE=$(uname -n)

# This file contains 4 alternatives, please use only one.

## Alternative 1, Minimal configuration, no VCL
# Listen on port 6081, administration on localhost:6082, and forward to
# content server on localhost:8080. Use a 1GB fixed-size cache file.
# DAEMON_OPTS=”-a :6081 \
# -T localhost:6082 \
# -b localhost:8080 \
# -u varnish -g varnish \
# -S /etc/varnish/secret \
# -s file,/var/lib/varnish/$INSTANCE/varnish_storage.bin,1G”

## Alternative 2, Configuration with VCL
# Listen on port 6081, administration on localhost:6082, and forward to
# one content server selected by the vcl file, based on the request.
# Use a 256MB memory based cache.
DAEMON_OPTS=”-a :80 \
-T localhost:6082 \
-f /etc/varnish/default.vcl \
-S /etc/varnish/secret \
-s malloc,256m”

## Alternative 3, Advanced configuration
# See varnishd(1) for more information.
# # Main configuration file. You probably want to change it 🙂
# VARNISH_VCL_CONF=/etc/varnish/default.vcl
# # Default address and port to bind to
# # Blank address means all IPv4 and IPv6 interfaces, otherwise specify
# # a host name, an IPv4 dotted quad, or an IPv6 address in brackets.
# # Telnet admin interface listen address and port
# # The minimum number of worker threads to start
# # The Maximum number of worker threads to start
# # Idle timeout for worker threads
# # Cache file location
# VARNISH_STORAGE_FILE=/var/lib/varnish/$INSTANCE/varnish_storage.bin
# # Cache file size: in bytes, optionally using k / M / G / T suffix,
# # or in percentage of available disk space using the % suffix.
# # File containing administration secret
# VARNISH_SECRET_FILE=/etc/varnish/secret
# # Backend storage specification
# # Default TTL used when the backend does not specify one
# # DAEMON_OPTS is used by the init script. If you add or remove options, make
# # sure you update this section, too.
# -t ${VARNISH_TTL} \
# -p thread_pool_min=${VARNISH_MIN_THREADS} \
# -p thread_pool_max=${VARNISH_MAX_THREADS} \
# -p thread_pool_timeout=${VARNISH_THREAD_TIMEOUT} \

## Alternative 4, Do It Yourself

nano /etc/varnish/default.vcl

# Configuration file for varnish (optimized for WordPress)

vcl 4.0;

backend server1 {
.host = “”;
.port = “8080”;
.connect_timeout = 600s;
.first_byte_timeout = 600s;
.between_bytes_timeout = 600s;
.max_connections = 800;

#Only allow purging from specific IPs
acl purge {

# This function is used when a request is send by a HTTP client (Browser)
##########START SUB VCL_RECV##########
sub vcl_recv {
#Normalize the header, remove the port (in case you’re testing this on various TCP ports)
set req.http.Host = regsub(req.http.Host, “:[0-9]+”, “”);

#Which pages to cache
if(!(req.http.host ~ “domain1.com“) &&
!(req.http.host ~ “domain2.com“) &&
!(req.http.host ~ “domain3.com“)) {
return (pass);

if ( (req.http.host ~ “^(?i)domain1.com” || req.http.host ~ “^(?i)www.domain1.com“) && req.http.X-Forwarded-Proto !~ “(?i)https”) {
return (synth(751, “”));

if ( (req.http.host ~ “^(?i)domain2.com” || req.http.host ~ “^(?i)www.domain2.com“) && req.http.X-Forwarded-Proto !~ “(?i)https”) {
return (synth(750, “”));

if ( (req.http.host ~ “^(?i)domain3.com” || req.http.host ~ “^(?i)www.domain3.com“) && req.http.X-Forwarded-Proto !~ “(?i)https”) {
return (synth(752, “”));

#Set client IP to headers
if (req.restarts == 0) {
if (req.http.x-forwarded-for) {
set req.http.X-Forwarded-For =
req.http.X-Forwarded-For + “, ” + client.ip;
} else {
set req.http.X-Forwarded-For = client.ip;

#Allow purging from ACL
if (req.method == “PURGE”) {
#If not allowed then a error 405 is returned
if (!client.ip ~ purge) {
return(synth(405, “This IP is not allowed to send PURGE requests.”));
#If allowed, do a cache_lookup -> vlc_hit() or vlc_miss()
return (purge);

#Post requests will not be cached
if (req.http.Authorization || req.method == “POST”) {
return (pass);

#———-Wordpress specific configuration———-“

#Do not cache the RSS feed
if (req.url ~ “/feed”) {
return (pass);

#Blitz hack
if (req.url ~ “/mu-.*”) {
return (pass);

#Do not cache the admin and login pages
if (req.url ~ “/wp-(login|admin)”) {
return (pass);

#Do not cache the WooCommerce pages
if (req.url ~ “/(cart|my-account|checkout|addons|/?add-to-cart=)”) {
return (pass);

#Remove the “has_js” cookie
set req.http.Cookie = regsuball(req.http.Cookie, “has_js=[^;]+(; )?”, “”);

#Remove any Google Analytics based cookies
set req.http.Cookie = regsuball(req.http.Cookie, “__utm.=[^;]+(; )?”, “”);

#Remove the Quant Capital cookies (added by some plugin, all __qca)
set req.http.Cookie = regsuball(req.http.Cookie, “__qc.=[^;]+(; )?”, “”);

#Remove the wp-settings-1 cookie
set req.http.Cookie = regsuball(req.http.Cookie, “wp-settings-1=[^;]+(; )?”, “”);

#Remove the wp-settings-time-1 cookie
set req.http.Cookie = regsuball(req.http.Cookie, “wp-settings-time-1=[^;]+(; )?”, “”);

#Remove the wp test cookie
set req.http.Cookie = regsuball(req.http.Cookie, “wordpress_test_cookie=[^;]+(; )?”, “”);

#Are there cookies left with only spaces or that are empty?
if (req.http.cookie ~ “^ *$”) {
unset req.http.cookie;

#Cache the following files extensions
if (req.url ~ “\.(css|js|png|gif|jp(e)?g|swf|ico)”) {
unset req.http.cookie;

#Normalize Accept-Encoding header and compression
if (req.http.Accept-Encoding) {
#Do not compress compressed files …
if (req.url ~ “\.(jpg|png|gif|gz|tgz|bz2|tbz|mp3|ogg)$”) {
unset req.http.Accept-Encoding;
} elsif (req.http.Accept-Encoding ~ “gzip”) {
set req.http.Accept-Encoding = “gzip”;
} elsif (req.http.Accept-Encoding ~ “deflate”) {
set req.http.Accept-Encoding = “deflate”;
} else {
unset req.http.Accept-Encoding;

#Check the cookies for WordPress-specific items
if (req.http.Cookie ~ “wordpress_” || req.http.Cookie ~ “comment_”) {
return (pass);
if (!req.http.cookie) {
unset req.http.cookie;

#———-End of WordPress specific configuration———-#

#Do not cache HTTP authentication and HTTP Cookie
if (req.http.Authorization || req.http.Cookie) {
# Not cacheable by default
return (pass);

#Cache all others requests
return (hash);
#########END SUB_VCL_RECV##########

sub vcl_synth {
if (resp.status == 751) {
set resp.status = 301;
set resp.http.Location = “https://domain1.com” + req.url;

if (resp.status == 750) {
set resp.status = 301;
set resp.http.Location = “https://domain2.com” + req.url;

if (resp.status == 752) {
set resp.status = 301;
set resp.http.Location = “https://domain3.com + req.url;


sub vcl_pipe {
return (pipe);

sub vcl_pass {
return (fetch);

#The data on which the hashing will take place
sub vcl_hash {
if (req.http.host) {
} else {

#Include the X-Forward-Proto header, since we want to treat HTTPS
# requests differently, and make sure this header is always passed
# properly to the backend server.
if (req.http.X-Forwarded-Proto) {

#If the client supports compression, keep that in a different cache
if (req.http.Accept-Encoding) {
return (lookup);

#This function is used when a request is sent by our backend (Nginx server)
##########START VCL_BACKEND_RESPONSE##########
sub vcl_backend_response {
#Remove some headers we never want to see
unset beresp.http.Server;
unset beresp.http.X-Powered-By;

#For static content strip all backend cookies
if (bereq.url ~ “\.(css|js|png|gif|jp(e?)g)|swf|ico”) {
unset beresp.http.cookie;

#Only allow cookies to be set if we’re in admin area
if (beresp.http.Set-Cookie && bereq.url !~ “^/wp-(login|admin)”) {
unset beresp.http.Set-Cookie;

#Do not cache response to posted requests or those with basic auth
if ( bereq.method == “POST” || bereq.http.Authorization ) {
set beresp.uncacheable = true;
set beresp.ttl = 120s;
return (deliver);

#Do not cache search results
if ( bereq.url ~ “\?s=” ){
set beresp.uncacheable = true;
set beresp.ttl = 120s;
return (deliver);

#Only cache status ok
if ( beresp.status != 200 ) {
set beresp.uncacheable = true;
set beresp.ttl = 120s;
return (deliver);

#A TTL of 24h
set beresp.ttl = 24h;
#Define the default grace period to serve cached content
set beresp.grace = 30s;

return (deliver);
##########END VCL_BACKEND_RESPONSE##########

##########START VCL_DELIVER##########
sub vcl_deliver {
if (obj.hits > 0) {
set resp.http.X-Cache = “cached”;
} else {
set resp.http.x-Cache = “uncached”;

#Remove some headers: PHP version
unset resp.http.X-Powered-By;

#Remove some headers: Apache\Nginx version & OS
unset resp.http.Server;

#Remove some headers: Varnish
unset resp.http.Via;
unset resp.http.X-Varnish;

return (deliver);
##########END VCL_DELIVER##########

sub vcl_init {
return (ok);

sub vcl_fini {
return (ok);

8. Set other config files:


nano /etc/php5/fpm/php.ini

display_errors = off
log_errors: on
max_input_time: 60
output_buffering: 4096
register_argc_argv: off
request_order: GP
session.bug_compat_42: off
session.bug_compat_warn: off
session.gc_divisor: 1000
session.hash_bits_per_character: 5
short_open_tag: off
variables_order: GPCS
engine = On
short_open_tag = Off
asp_tags = Off
precision = 14
output_buffering = 4096
zlib.output_compression = Off
implicit_flush = Off
unserialize_callback_func =
serialize_precision = 17
disable_classes =
ignore_user_abort = Off
zend.enable_gc = On
expose_php = Off
max_execution_time = 120
max_input_time = 60
memory_limit = 64M
error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT
display_errors = Off
display_startup_errors = Off
log_errors = On
log_errors_max_len = 1024
ignore_repeated_errors = Off
ignore_repeated_source = Off
report_memleaks = On
track_errors = Off
html_errors = On
variables_order = “GPCS”
request_order = “GP”
register_argc_argv = Off
auto_globals_jit = On
post_max_size = 15M
auto_prepend_file =
auto_append_file =
default_mimetype = “text/html”
default_charset = “UTF-8”
doc_root =
user_dir =
enable_dl = Off
file_uploads = On
upload_max_filesize = 15M
max_file_uploads = 20
allow_url_fopen = Off
allow_url_include = Off
default_socket_timeout = 30
[CLI Server]
cli_server.color = On
pdo_mysql.cache_size = 2000
[mail function]
SMTP = localhost
smtp_port = 25
mail.add_x_header = On
sql.safe_mode = Off
odbc.allow_persistent = On
odbc.check_persistent = On
odbc.max_persistent = -1
odbc.max_links = -1
odbc.defaultlrl = 4096
odbc.defaultbinmode = 1
ibase.allow_persistent = 1
ibase.max_persistent = -1
ibase.max_links = -1
ibase.timestampformat = “%Y-%m-%d %H:%M:%S”
ibase.dateformat = “%Y-%m-%d”
ibase.timeformat = “%H:%M:%S”
mysql.allow_local_infile = On
mysql.allow_persistent = Off
mysql.cache_size = 2000
mysql.max_persistent = -1
mysql.max_links = -1
mysql.default_port =
mysql.default_socket =
mysql.default_host =
mysql.default_user =
mysql.default_password =
mysql.connect_timeout = 60
mysql.trace_mode = Off
mysqli.max_persistent = -1
mysqli.allow_persistent = On
mysqli.max_links = -1
mysqli.cache_size = 2000
mysqli.default_port = 3306
mysqli.default_socket =
mysqli.default_host =
mysqli.default_user =
mysqli.default_pw =
mysqli.reconnect = Off
mysqlnd.collect_statistics = On
mysqlnd.collect_memory_statistics = Off
pgsql.allow_persistent = On
pgsql.auto_reset_persistent = Off
pgsql.max_persistent = -1
pgsql.max_links = -1
pgsql.ignore_notice = 0
pgsql.log_notice = 0
sybct.allow_persistent = On
sybct.max_persistent = -1
sybct.max_links = -1
sybct.min_server_severity = 10
sybct.min_client_severity = 10
bcmath.scale = 0
session.save_handler = files
session.use_strict_mode = 0
session.use_cookies = 1
session.use_only_cookies = 1
session.name = PHPSESSID
session.auto_start = 0
session.cookie_lifetime = 0
session.cookie_path = /
session.cookie_domain =
session.cookie_httponly =
session.serialize_handler = php
session.gc_probability = 0
session.gc_divisor = 1000
session.gc_maxlifetime = 1440
session.bug_compat_42 = Off
session.bug_compat_warn = Off
session.referer_check =
session.cache_limiter = nocache
session.cache_expire = 180
session.use_trans_sid = 0
session.hash_function = 0
session.hash_bits_per_character = 5
url_rewriter.tags = “a=href,area=href,frame=src,input=src,form=fakeentry”
mssql.allow_persistent = On
mssql.max_persistent = -1
mssql.max_links = -1
mssql.min_error_severity = 10
mssql.min_message_severity = 10
mssql.compatibility_mode = Off
mssql.secure_connection = Off
tidy.clean_output = Off
soap.wsdl_cache_limit = 5
ldap.max_links = -1

nano /etc/php5/fpm/pool.d/domain.example.pool.conf

; Use this config only if you want to run separate php5-fpm process per user
; Pool name, the variable $pool can be used in any directive and will be replaced by the
; pool name (‘www’ here)

; Per pool prefix
; It only applies on the following directives:
; – ‘slowlog’
; – ‘listen’ (unixsocket)
; – ‘chroot’
; – ‘chdir’
; – ‘php_values’
; – ‘php_admin_values’
; When not set, the global prefix (or /usr) applies instead.
; Note: This directive can also be relative to the global prefix.
; Default Value: none
;prefix = /path/to/pools/$pool

; The address on which to accept FastCGI requests.
; Valid syntaxes are:
; ‘ip.add.re.ss:port’ – to listen on a TCP socket to a specific address on
; a specific port;
; ‘port’ – to listen on a TCP socket to all addresses on a
; specific port;
; ‘/path/to/unix/socket’ – to listen on a unix socket.
; Note: This value is mandatory.
listen = /var/run/example.domain_fpm.sock

; Set listen(2) backlog. A value of ‘-1’ means unlimited.
; Default Value: 128 (-1 on FreeBSD and OpenBSD)
;listen.backlog = -1

; List of ipv4 addresses of FastCGI clients which are allowed to connect.
; Equivalent to the FCGI_WEB_SERVER_ADDRS environment variable in the original
; PHP FCGI (5.2.2+). Makes sense only with a tcp listening socket. Each address
; must be separated by a comma. If this value is left blank, connections will be
; accepted from any ip address.
; Default Value: any
;listen.allowed_clients =

; Set permissions for unix socket, if one is used. In Linux, read/write
; permissions must be set in order to allow connections from a web server. Many
; BSD-derived systems allow connections regardless of permissions.
; Default Values: user and group are set as the running user
; mode is set to 0666
listen.owner = user
listen.group = user
listen.mode = 0660

; Unix user/group of processes
; Note: The user is mandatory. If the group is not set, the default user’s group
; will be used.
user = user
group = user

; Choose how the process manager will control the number of child processes.
; Possible Values:
; static – a fixed number (pm.max_children) of child processes;
; dynamic – the number of child processes are set dynamically based on the
; following directives:
; pm.max_children – the maximum number of children that can
; be alive at the same time.
; pm.start_servers – the number of children created on startup.
; pm.min_spare_servers – the minimum number of children in ‘idle’
; state (waiting to process). If the number
; of ‘idle’ processes is less than this
; number then some children will be created.
; pm.max_spare_servers – the maximum number of children in ‘idle’
; state (waiting to process). If the number
; of ‘idle’ processes is greater than this
; number then some children will be killed.
; Note: This value is mandatory.
pm = dynamic

; The number of child processes to be created when pm is set to ‘static’ and the
; maximum number of child processes to be created when pm is set to ‘dynamic’.
; This value sets the limit on the number of simultaneous requests that will be
; served. Equivalent to the ApacheMaxClients directive with mpm_prefork.
; Equivalent to the PHP_FCGI_CHILDREN environment variable in the original PHP
; CGI.
; Note: Used when pm is set to either ‘static’ or ‘dynamic’
; Note: This value is mandatory.
pm.max_children = 1

; The number of child processes created on startup.
; Note: Used only when pm is set to ‘dynamic’
; Default Value: min_spare_servers + (max_spare_servers – min_spare_servers) / 2
pm.start_servers = 1

; The desired minimum number of idle server processes.
; Note: Used only when pm is set to ‘dynamic’
; Note: Mandatory when pm is set to ‘dynamic’
pm.min_spare_servers = 1

; The desired maximum number of idle server processes.
; Note: Used only when pm is set to ‘dynamic’
; Note: Mandatory when pm is set to ‘dynamic’
pm.max_spare_servers = 1

; The number of requests each child process should execute before respawning.
; This can be useful to work around memory leaks in 3rd party libraries. For
; endless request processing specify ‘0’. Equivalent to PHP_FCGI_MAX_REQUESTS.
; Default Value: 0
pm.max_requests = 500

; The URI to view the FPM status page. If this value is not set, no URI will be
; recognized as a status page. By default, the status page shows the following
; information:
; accepted conn – the number of request accepted by the pool;
; pool – the name of the pool;
; process manager – static or dynamic;
; idle processes – the number of idle processes;
; active processes – the number of active processes;
; total processes – the number of idle + active processes.
; max children reached – number of times, the process limit has been reached,
; when pm tries to start more children (works only for
; pm ‘dynamic’)
; The values of ‘idle processes’, ‘active processes’ and ‘total processes’ are
; updated each second. The value of ‘accepted conn’ is updated in real time.
; Example output:
; accepted conn: 12073
; pool: www
; process manager: static
; idle processes: 35
; active processes: 65
; total processes: 100
; max children reached: 1
; By default the status page output is formatted as text/plain. Passing either
; ‘html’, ‘xml’ or ‘json’ as a query string will return the corresponding output
; syntax. Example:
; http://www.foo.bar/status
; http://www.foo.bar/status?json
; http://www.foo.bar/status?html
; http://www.foo.bar/status?xml
; Note: The value must start with a leading slash (/). The value can be
; anything, but it may not be a good idea to use the .php extension or it
; may conflict with a real PHP file.
; Default Value: not set
;pm.status_path = /status

; The ping URI to call the monitoring page of FPM. If this value is not set, no
; URI will be recognized as a ping page. This could be used to test from outside
; that FPM is alive and responding, or to
; – create a graph of FPM availability (rrd or such);
; – remove a server from a group if it is not responding (load balancing);
; – trigger alerts for the operating team (24/7).
; Note: The value must start with a leading slash (/). The value can be
; anything, but it may not be a good idea to use the .php extension or it
; may conflict with a real PHP file.
; Default Value: not set
;ping.path = /ping

; This directive may be used to customize the response of a ping request. The
; response is formatted as text/plain with a 200 response code.
; Default Value: pong
;ping.response = pong

; The access log file
; Default: not set
;access.log = log/$pool.access.log

; The access log format.
; The following syntax is allowed
; %%: the ‘%’ character
; %C: %CPU used by the request
; it can accept the following format:
; – %{user}C for user CPU only
; – %{system}C for system CPU only
; – %{total}C for user + system CPU (default)
; %d: time taken to serve the request
; it can accept the following format:
; – %{seconds}d (default)
; – %{miliseconds}d
; – %{mili}d
; – %{microseconds}d
; – %{micro}d
; %e: an environment variable (same as $_ENV or $_SERVER)
; it must be associated with embraces to specify the name of the env
; variable. Some exemples:
; – server specifics like: %{REQUEST_METHOD}e or %{SERVER_PROTOCOL}e
; – HTTP headers like: %{HTTP_HOST}e or %{HTTP_USER_AGENT}e
; %f: script filename
; %l: content-length of the request (for POST request only)
; %m: request method
; %M: peak of memory allocated by PHP
; it can accept the following format:
; – %{bytes}M (default)
; – %{kilobytes}M
; – %{kilo}M
; – %{megabytes}M
; – %{mega}M
; %n: pool name
; %o: ouput header
; it must be associated with embraces to specify the name of the header:
; – %{Content-Type}o
; – %{X-Powered-By}o
; – %{Transfert-Encoding}o
; – ….
; %p: PID of the child that serviced the request
; %P: PID of the parent of the child that serviced the request
; %q: the query string
; %Q: the ‘?’ character if query string exists
; %r: the request URI (without the query string, see %q and %Q)
; %R: remote IP address
; %s: status (response code)
; %t: server time the request was received
; it can accept a strftime(3) format:
; %d/%b/%Y:%H:%M:%S %z (default)
; %T: time the log has been written (the request has finished)
; it can accept a strftime(3) format:
; %d/%b/%Y:%H:%M:%S %z (default)
; %u: remote user
; Default: “%R – %u %t \”%m %r\” %s”
;access.format = %R – %u %t “%m %r%Q%q” %s %f %{mili}d %{kilo}M %C%%

; The timeout for serving a single request after which the worker process will
; be killed. This option should be used when the ‘max_execution_time’ ini option
; does not stop script execution for some reason. A value of ‘0’ means ‘off’.
; Available units: s(econds)(default), m(inutes), h(ours), or d(ays)
; Default Value: 0
request_terminate_timeout = 30s

; The timeout for serving a single request after which a PHP backtrace will be
; dumped to the ‘slowlog’ file. A value of ‘0s’ means ‘off’.
; Available units: s(econds)(default), m(inutes), h(ours), or d(ays)
; Default Value: 0
;request_slowlog_timeout = 0

; The log file for slow requests
; Default Value: not set
; Note: slowlog is mandatory if request_slowlog_timeout is set
;slowlog = log/$pool.log.slow

; Set open file descriptor rlimit.
; Default Value: system defined value
;rlimit_files = 1024

; Set max core size rlimit.
; Possible Values: ‘unlimited’ or an integer greater or equal to 0
; Default Value: system defined value
;rlimit_core = 0

; Chroot to this directory at the start. This value must be defined as an
; absolute path. When this value is not set, chroot is not used.
; Note: you can prefix with ‘$prefix’ to chroot to the pool prefix or one
; of its subdirectories. If the pool prefix is not set, the global prefix
; will be used instead.
; Note: chrooting is a great security feature and should be used whenever
; possible. However, all PHP paths will be relative to the chroot
; (error_log, sessions.save_path, …).
; Default Value: not set
;chroot =

; Chdir to this directory at the start.
; Note: relative path can be used.
; Default Value: current directory or / when chroot
chdir = /

; Redirect worker stdout and stderr into main error log. If not set, stdout and
; stderr will be redirected to /dev/null according to FastCGI specs.
; Note: on highloaded environement, this can cause some delay in the page
; process time (several ms).
; Default Value: no
;catch_workers_output = yes

; Pass environment variables like LD_LIBRARY_PATH. All $VARIABLEs are taken from
; the current environment.
; Default Value: clean env
;env[PATH] = /usr/local/bin:/usr/bin:/bin
;env[TMP] = /tmp
;env[TMPDIR] = /tmp
;env[TEMP] = /tmp

; Additional php.ini defines, specific to this pool of workers. These settings
; overwrite the values previously defined in the php.ini. The directives are the
; same as the PHP SAPI:
; php_value/php_flag – you can set classic ini defines which can
; be overwritten from PHP call ‘ini_set’.
; php_admin_value/php_admin_flag – these directives won’t be overwritten by
; PHP call ‘ini_set’
; For php_*flag, valid values are on, off, 1, 0, true, false, yes or no.

; Defining ‘extension’ will load the corresponding shared extension from
; extension_dir. Defining ‘disable_functions’ or ‘disable_classes’ will not
; overwrite previously defined php.ini values, but will append the new value
; instead.

; Note: path INI options can be relative and will be expanded with the prefix
; (pool, global or /usr)

; Default Value: nothing is defined by default except the values in php.ini and
; specified at startup with the -d argument
;php_admin_value[sendmail_path] = /usr/sbin/sendmail -t -i -f www@my.domain.com
;php_flag[display_errors] = off
;php_admin_value[error_log] = /var/log/fpm-php.www.log
;php_admin_flag[log_errors] = on
;php_admin_value[memory_limit] = 32M
php_admin_value[session.save_path] = “/home/user/_sessions”
php_admin_value[open_basedir] = “/home/user:/usr/share/pear:/usr/share/php:/tmp:/usr/local/lib/php”

php_flag[display_errors] = off
php_admin_value[error_reporting] = 0
php_admin_value[error_log] = /var/log/php5-fpm.log
php_admin_flag[log_errors] = on
php_admin_value[memory_limit] = 128M

9. Add SSL to WordPress:

Go to your WordPress web folder, open file wp-config.php and add:

define(‘FORCE_SSL_ADMIN’, true);

define(‘FORCE_SSL_LOGIN’, true);

if ($_SERVER[‘HTTP_X_FORWARDED_PROTO’] == ‘https’)


Important! Add this before the line “require_once(ABSPATH . ‘wp-settings.php’);”, otherwise you’ll get “You do not have sufficient permissions to access this page” message when trying to login into admin (wp-admin).

10. Restart services


service nginx restart

service php5-fpm restart

service varnish restart

My original post is at: https://troubleshoot-coltpython.blogspot.com/2013/11/measuring-raspberrypi-cpu-and-gpu.html

We’ll create a bash script:

sudo nano temp.sh

Paste this and save:

#!/bin/bash cpuTemp0=$(cat /sys/class/thermal/thermal_zone0/temp) cpuTemp1=$(($cpuTemp0/1000)) cpuTemp2=$(($cpuTemp0/100)) cpuTempM=$(($cpuTemp2 % $cpuTemp1)) cpuFreq=`cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq | sed 's/.\{3\}$//'`Mhz echo CPU temp"="$cpuTemp1"."$cpuTempM"'C" echo GPU $(/opt/vc/bin/vcgencmd measure_temp) echo CPU frequency=$cpuFreq

Set permission and run it:

sudo chmod +x temp.sh;./temp.sh

Duplicate of my original post: https://troubleshoot-coltpython.blogspot.com/2013/11/bash-script-domain-expiration-check.html

This script checks if domain has expired and also supports e-mail notice. 

Original script is from Matt (domain-check). I modifed already modified script from Vivek (http://www.cyberciti.biz/tips/domain-check-script.html).

List of changes:

– Added support for Slovenian (.si) domains.

– Added VAR for “mail from:”.
– Increased time between whois queries to 7 seconds (Slovenian whois only allows 10 queries per minute).
– Mail subject and mail data are translated to Slovene language.
– Changed default VAR path for mail (/bin/mail/ to /usr/bin/mail)

Get the script:

wget http://krejzi.si/files/domain-check;mv domain-check domain-check.sh;chmod +x domain-check.sh

Mirror: https://pastebin.com/pQe3RmDz


Usage: ./domain-check.sh [ -e email ] [ -x expir_days ] [ -q ] [ -a ] [ -h ] {[ -d domain_namee ]} || { -f domainfile}

  -a                                   : Send a warning message through email
  -d domain                            : Domain to analyze (interactive mode)
  -e email address                     : Email address to send expiration notices
  -f domain file                       : File with a list of domains
  -h                                   : Print this screen
  -s whois server                      : Whois sever to query for information
  -q                                   : Don't print anything on the console
  -x days                              : Domain expiration interval (eg. if domain_date < days)

Check single domain:

./domain-check.sh -d domain.si

Check single domain and send e-mail notice if domain will expire in less than defined in WARNDAYS:

./domain-check.sh -a -d domain.si

You can also check multiple domains. Create a file domains.txt and add your domains like:



Check multiple domains:

./domain-check.sh -f domains.txt

Check multiple domains and send e-mail notice if any domain will expire in less than defined in WARNDAYS:

./domain-check.sh -a -f domains.txt

Set your own parameters from command line. Next command will check domains in domains.txt with expiration date less than 60 days and send e-mail notice to admin@domain.si

./domain-check.sh -a -f domains.txt -e admin@domain.si -x 60 

If you are going to use this script with cron, use -q option or change QUIET=”FALSE” to QUIET=”TRUE”

Let’s say we want to run this script every day at 4 AM. Open crontab with crontab -e (Ubuntu) and add the following line:

0 4 * * * /path/to/your/script/domain-check.sh -q -a -f /path/to/your/list/domains.txt