Host your own web app on a Raspberry Pi Part 2

Make it public firs step: NAT Port Forwarding
 
So, you are probably wondering how this amazing web application can be visible from the world wide web. Well, no need to wonder no more.
First you need to enable a NAT port forwarding in your router to redirect traffic arriving on your public IP address to your RaspberryPi on your local network. 
If you need more information regarding public IP addresses and NAT-Firewall you can get some background information on this link.
To actually enable a Port Forwarding on your specific home router you need to go to your router administration interface via a web browser – just key in the IP for your router and log in. My router is available via the IP 192.168.2.1.
 
The settings for Port Forwarding is mostly hidden under some kind of advanced setup option. You’d need to create a rule based on this screenshot below:

Port forwarding settings


The external port (range) is the port you want your website to be available on. For websites, the standard port is 80. The internal port range is the port your webserver – in this case Apache - is running on your local RaspberryPi. If you didn’t change anything in the setup from Part 1 of this tutorial, this should also be Port 80. The internal IP address is the IP of your Pi on your local network.

Dollar bills, Dollar bills - purchase a domain

If you want to access a website, normally you key in a name in your browser (actually, most of the time one just googles for the site), you don’t want to remember a IP address of a website. This is when a domain comes in handy. A domain is basically a unique identifier which links to the IP address of your network resource – in this case our public IP address. 
Since this domain is unique, there needs to be some kind of organization which keeps track of the assigned domains. This is why this is the only step in our tutorial where we need to spend a little money. But you can get a regular domain for approx. 10-20 $ a year – this equals two Starbucks coffees, you can afford that, right?

There are literally hundreds of companies which sell domains – since I am based in Germany, I got my domain from Ionos https://www.ionos.com (former 1&1), but of course, you can buy your domain from a different company. 
Just compare the prices and take the cheapest. Be careful though, mostly you buy a domain within a 12 months subscription. Some companies lure you in with cheap prices for the first 12 months and after your first year, the prices increase significantly.

Dynamic DNS via Cloudflare

The problem with your public IP is, that it can change from time to time. For the normal internet user this is not a problem, but since you are now a power user, this circumstance is getting problematic for you. 
In order to have your website always available under the same IP you need a dynamic DNS service that always resolves the host name to your current public IP. There are many apps which offer exact this kind of service, but unfortunately, they restricted their free use in the past. At the time of writing, with most of the services it is not possible to connect your own domain while using the free plan, e.g. you’d need to use a subdomain of the service. But we want our web app to be available under our own carefully selected domain.
This is where cloudflare comes into play.
Cloudflare is a service which offers protection against DDoS-attacks and much more. It also offers a DNS service and an API to refresh DNS entries via a script. 
The idea is that our PI checks for our public IP address cyclically and sends it to the cloudflare API. The cloudflare api manages our domain DNS and connects our public IP with the domain.

Get a cloudflare account

You can create a free cloudflare account on cloudflare.com. You don’t need to add any personal information or payment methods. Your email and a password is fine.
You can click on “Add a website” and enter your domain name

Add your domain


After this step, you can choose a plan – for our needs, the free plan is sufficient:

Select a plan

After confirming your free plan, cloudflare automatically scans all DNS entries for your domain. You can check the results of the scan and add entries if it’s not correct or complete.
You can click on continue and cloudflare displays now the nameservers you’d need to add to your Domain DNS settings at your domain registration service (in my case this was Ionos).

The DNS-servers you need to add are the following: 

wally.ns.cloudflare.com

oswald.ns.cloudflare.com

To change your domain DNS settings you need to go back to your registration service and go to your domain settings. At Ionos this setting is called “mode of use” -> custom nameserver.
(sorry for the german screenshot, but Ionos only offers a local language UI)
Add your custom DNS nameserver at your domain registry service


Please keep in mind that the DNS changes take some time to be published through all major DNS servers. It can take up to 24hours.
After your DNS changes are published we can start working with the cloudflare API in order to update our IP by a script. 
First you need to create a cloudflare API key.

Go to:
Overview > Get your API key > Global API Key in the cloudflare backend and store your API key to a safe place. 


To make life a little bit easier, you can use the following BASH script to update your IP - You can ssh into your PI and create a new file called updateIpCloudflare.sh and paste this content into it:

$ nano updateIpCloudflare.sh

#!/bin/bash

# CHANGE THESE
auth_email="[email protected]"
auth_key="yourCloudFlareAPIKey"
zone_name="aboe.eu"
record_name="aboe.eu"

ip=$(curl -s http://ipv4.icanhazip.com)
echo $ip
ip_file="ip.txt"
id_file="cloudflare.ids"
log_file="cloudflare.log"

# LOGGER
log() {
    if [ "$1" ]; then
        echo -e "[$(date)] - $1" >> $log_file
    fi
}

# SCRIPT START
log "Check Initiated"

if [ -f $ip_file ]; then
    old_ip=$(cat $ip_file)
    if [ $ip == $old_ip ]; then
        echo "IP has not changed."
        exit 0
    fi
fi

if [ -f $id_file ] && [ $(wc -l $id_file | cut -d " " -f 1) == 2 ]; then
    zone_identifier=$(head -1 $id_file)
    record_identifier=$(tail -1 $id_file)
else
    zone_identifier=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones?name=$zone_name" -H "X-Auth-Email: $auth_email" -H "X-Auth-Key: $auth_key" -H "Content-Type: application/json" | grep -Po '(?<="id":")[^"]*' | head -1 )
    record_identifier=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$zone_identifier/dns_records?name=$record_name" -H "X-Auth-Email: $auth_email" -H "X-Auth-Key: $auth_key" -H "Content-Type: application/json"  | grep -Po '(?<="id":")[^"]*')
    echo "$zone_identifier" > $id_file
    echo "$record_identifier" >> $id_file
fi

update=$(curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/$zone_identifier/dns_records/$record_identifier" -H "X-Auth-Email: $auth_email" -H "X-Auth-Key: $auth_key" -H "Content-Type: application/json" --data "{\"id\":\"$zone_identifier\",\"type\":\"A\",\"name\":\"$record_name\",\"content\":\"$ip\"}")

if [[ $update == *"\"success\":false"* ]]; then
    message="API UPDATE FAILED. DUMPING RESULTS:\n$update"
    log "$message"
    echo -e "$message"
    exit 1
else
    message="IP changed to: $ip"
    echo "$ip" > $ip_file
    log "$message"
    echo "$message"
fi
    
To execute the script, you’d need to make it executable:

$ chmod +x updateIpCloudflare.sh

You need to change the credentials on the top of the script. Please change your email and your API-key accordingly:

# CHANGE THESE
auth_email="[email protected]"
auth_key="yourCloudFlareAPIKey"
zone_name="aboe.eu"
record_name="aboe.eu"

The script checks your public IP and saves it into a file “ip.txt”. If the IP in the file is different from your public IP, it will update your IP in cloudflare via CURL.

If you run the script via:
$ ./updateIpCloudflare.sh

Since this is the first run, it will create the IP file, a cloudflare-ID file and a log. You should see the following Output – of course the your IP is a different one:

10.10.215.10

IP changed to: 10.10.215.10

You can also check the log file in the same directory:

$ less cloudflare.log

[Thu May 14 13:06:46 BST 2020] - Check Initiated

[Thu May 14 13:06:47 BST 2020] - IP changed to: 10.10.215.10

This log output tells us that the API call was successful and our public IP was updated in the cloudflare DNS service. Our domain points now to the correct IP.
If you key in your domain name into the browser address bar you should be greeted by our example Laravel web app:

http://my-domain.com

hello


One last thing

You probably already noticed, but we only started our updateIpCloudflare.sh script manually and it wouldn’t recognize if our IP has changed. This is why we need to run the script periodically. The easiest way to do this is via a cronjob.
Open the crontab and add the following line to the end of the file:

$ sudo crontab -e

*/10 *    * * *   /home/pi/updateIpCloudflare.sh > /dev/null 2>&1

Now the OS will start the script ever 10 minutes automatically. Worst case scenario would be that our website is not available for 10 minutes, if the public IP changed right after the last run of the script. But I think that’s okay.


That's all for today - In the next step of this tutorial, we'll create a free SSL-certificate with the help of Letsencrypt and make our web app available via https. But as always, please let me know if you have any problems, suggestions or feedback in the comments down below.

Comments: