linux · raspberry pi · Websites

NginX on a Raspberry PI

rpi-logo-reg-screen.png

A little background…

Raspberry PIs are awesome as little web servers.  They are a limited in how much compute they have available.  So, if you are like me, you’ll have piles of these little guys sprinkled around your house.

The trouble is its a pain to get to all the things they can do for you from outside of your network.

Solutions for sharing the IP address…

There are many ways to share your internet connection, here are two… the first is common, the second is preferred.  Use the comments to tell me how you do it. 🙂

Port Forwarding…

Here is a little video a fellow put together on it:

portforwarding

The first, and usual goto, is to setup “port forwarding” on your router.  Most modern routers support this in some fashion.  Basically it says, “If someone connects to you on port X, route that traffic to computer at IP-ADDRESS on port Y.”

There are some drawbacks though.  Namely, you have to use a clunky config interface that you can’t customize, reuse, or version-control.

Enter NGINX…

The second way is to use a PI as the port router with NGINX.  NginX has been around for years.  Its a highly optimized web server used all over the place.  Mostly its used to route requests based on domains or in load balancing to a collection of services or hosts behind it.

This is typical:

1_TrNJZqECEj0eVuJDeNKtNQ

This high-quality, professional tool is free and easy to use.  I’ll show you how.

Installing NGINX on a Raspberry PI…

First steps though: Install NginX

sudo apt-get update
sudo apt-get install nginx

Now you need to update your nginx configuration file found here:

/etc/nginx/nginx.conf

Let’s say you have three PIs on your network.  All of them providing a web service at port 80, the config would look like this:

server {
    listen 80 default_server;
    listen [::]:80 default_server ipv6only=on;

    root /var/www/html;
    index index.html index.htm;
 
    location /announce/ {
       proxy_pass http://192.168.1.138:8080/;
    }

    location /pics/ {
      proxy_pass http://192.168.1.33/;
    }

    location /show/ {
      proxy_pass http://192.168.1.232/;
    }
    
    server_name _;

    location / {
      # First attempt to serve request as file, then
      # as directory, then fall back to displaying a 404.
      try_files $uri $uri/ =404;
    }
}

With that configuration – the main PI will now take queries to:

http://mainpi.local/
http://mainpi.local/announce/
http://mainpi.local/pics/
http://mainpi.local/show/

Where each of those URLs will now go to a different PI!!!

Now, for security, lets add user names and password access – then we’ll put TLS(SSL) into the server as well – so no one can snoop your traffic.

Creating usernames/passwords for your server…

Finally, lets create some user accounts, give them a password and VOILA – you now have a secure way to get at resources on your private network.

Also inspired through DigitalOcean tutorials:

How To Set Up Password Authentication with Nginx

Lets start by using the apache tools they mentioned:

sudo apt-get install apache2-utils

That lets us use the handy “htpasswd” tool to create our passwords file and add a user:

sudo htpasswd -c /etc/nginx/.htpasswd sammy

Now that the file “.htpasswd” exists, we can add users without the “-c” for create:

sudo htpasswd /etc/nginx/.htpasswd another_user

Using those passwords in NGINX:

server {
    listen 80 default_server;
    listen [::]:80 default_server ipv6only=on;

    root /var/www/html;
    index index.html index.htm;
 
    location /announce/ {
       proxy_pass http://192.168.1.138:8080/;
    }

    location /pics/ {
      proxy_pass http://192.168.1.33/;
      auth_basic "Restricted Content";
      auth_basic_user_file /etc/nginx/.htpasswd;
    }

    location /show/ {
      proxy_pass http://192.168.1.232/;
      auth_basic "Restricted Content";
      auth_basic_user_file /etc/nginx/.htpasswd;
    }
    
    server_name _;

    location / {
      # First attempt to serve request as file, then
      # as directory, then fall back to displaying a 404.
      try_files $uri $uri/ =404;
    }
}

Using SSL/TLS on your PI…

I used the training found here:

How To Create a Self-Signed SSL Certificate for Nginx

First, we need to generate our certificates:

sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/ssl/private/nginx-selfsigned.key -out /etc/ssl/certs/nginx-selfsigned.crt

Which asks a couple of questions.  Here are some example answers from DigitalOceans:

Country Name (2 letter code) [AU]:US
State or Province Name (full name) [Some-State]:New York
Locality Name (eg, city) []:New York City
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Bouncy Castles, Inc.
Organizational Unit Name (eg, section) []:Ministry of Water Slides
Common Name (e.g. server FQDN or YOUR name) []:server_IP_address
Email Address []:admin@your_domain.com

Then we need to generate this thing… I’m not sure I totally understand this part:

sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048

Then we need to create the config chunks, and include those into the base file.

Edit:  sudo vim /etc/nginx/snippets/self-signed.conf

ssl_certificate /etc/ssl/certs/nginx-selfsigned.crt;
ssl_certificate_key /etc/ssl/private/nginx-selfsigned.key;

Edit: sudo vim /etc/nginx/snippets/ssl-params.conf

# from https://cipherli.st/
# and https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html

ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
ssl_ecdh_curve secp384r1;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
# Disable preloading HSTS for now. You can use the commented out header line that includes
# the "preload" directive if you understand the implications.
#add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload";
add_header Strict-Transport-Security "max-age=63072000; includeSubdomains";
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;

ssl_dhparam /etc/ssl/certs/dhparam.pem;

Now we need to include these snippets into the original configuration file, and establish the redirect to use the SSL port.

server {
  listen 80 default_server;
  listen [::]:80 default_server;
  server_name server_domain_or_IP;
  return 302 https://$server_name$request_uri;
}

server {

  # SSL configuration

  listen 443 ssl http2 default_server;
  listen [::]:443 ssl http2 default_server;
  include snippets/self-signed.conf;
  include snippets/ssl-params.conf;

  . . .

The final configuration file…

server {
  listen 80 default_server;
  listen [::]:80 default_server;
  server_name server_domain_or_IP;
  return 302 https://$server_name$request_uri;
}

server {
  # SSL configuration
  listen 443 ssl http2 default_server;
  listen [::]:443 ssl http2 default_server;
  include /etc/nginx/snippets/self-signed.conf;
  include /etc/nginx/snippets/ssl-params.conf;

  root /var/www/html;
  location /announce/ {
    proxy_pass http://192.168.1.138:8080/;
  }

  location /pics/ {
    proxy_pass http://192.168.1.33/;
    auth_basic "Restricted Content";
    auth_basic_user_file /etc/nginx/.htpasswd;
  }

  location /show/ {
    proxy_pass http://192.168.1.232/;
    auth_basic "Restricted Content";
    auth_basic_user_file /etc/nginx/.htpasswd;
  }

  # Add index.php to the list if you are using PHP
  index index.html index.htm index.nginx-debian.html;

  server_name _;

  location / {
    # First attempt to serve request as file, then
    # as directory, then fall back to displaying a 404.
    try_files $uri $uri/ =404;
  }
}

Now check your file… and make sure all the configs are ok:

sudo nginx -t

There will be one warning you can ignore:

nginx: [warn] "ssl_stapling" ignored, issuer certificate not found
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

If you see that you are good! Time to restart the NginX Service and put it to work!

sudo systemctl restart nginx

Now.. go add port 80 and port 443 port forwarding to THIS PI alone.

Discovering/Tracking your public IP…

… lets say you want to always know your home IP.

There is a service running on myip.opendns.com that allows you to query it from the command line like this:

dig +short myip.opendns.com @resolver1.opendns.com

Things to know about that IP…

Things to know about that ip though… it changes. Unless your home ISP is selling you a static IP – they reserve the right to update your IP address at regular intervals (usually when your connection reboots.)

So if you are trying to let friends and family get access to resources within your firewall – you’ll need to give them a DNS entry that will resolve it for them. Another option is to teach them how to ssh-bastion or use ngrok.

There are quite a few steps to creating your own dynamic dns updates. But if you are the adventurous type AND you have an AWS account. Go make yourself a domain in Route53 – and then this little nugget of code is AWESOME. 😉 To use that code – here is a little script that pulls it together.

const route53 = require("./route53");
const { exec } = require('node:child_process');
console.log("Getting zone: ", route53);

function checkForIPUpdate() {
    try {
      exec('dig +short myip.opendns.com @resolver1.opendns.com', (error, stdout, stderr) => {
        if (error) {
          console.error(`exec error: ${error}`);
          return;
        }

        try {
            const data = {ip: stdout.replace(/\n/,"")};
            console.log("Got data: ", data);
            route53.retrieveHostedZoneId(<YOUR ZONE>).then(function(hostedZone) {
                console.log("Zone", hostedZone);
                route53.retrieveRecordSet(hostedZone.Id, <YOUR SUBDOMAIN>, function(err, recordSet) {
                    console.log("Record set", recordSet);
                    if (recordSet.ResourceRecords[0].Value !== data.ip) {
                        recordSet.ResourceRecords[0].Value = data.ip;
                        route53.updateRecordSet(hostedZone.Id, recordSet.Name, recordSet.Type, recordSet.ResourceRecords[0].Value, recordSet.TTL, function(err, res) {
                            console.log("It is updated...");
                        });
                    } else {
                        console.log("It is not updated... already up to date");
                    }
                });
            });
        } catch (e) {
            console.log("Error: ", e);
        }
      });
    } catch(ex) {
        console.log("Error contacting opendns: ", ex);
    }
}

setInterval(checkForIPUpdate, 1000 * 60 * 10); // 10 minutes
checkForIPUpdate();

(Stable diffusion’s version of a “secure website”… like what you have now. Fun. )

Advertisement

One thought on “NginX on a Raspberry PI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.