Install Let’s Encrypt SSL on Raspberry Pi

I love Let’s Encrypt. It’s free SSL, it’s safer because of the auto-renewal and it’s so easy to setup. No more emailing around validating company name and whatnot.

The installation on server level has also got a lot easier in the past years. The following will work for both Raspbian and Ubuntu.
For other systems check the official site, just select the correct OS and webserver.

Let’s Encrypt will work through a program called Certbot. We will install Certbot and that will get the free SSL certificates from Let’s Encrypt.

To install run these 2 commands, these might take a while:

sudo snap install core; sudo snap refresh core
sudo snap install --classic certbot

Once this is done we can start getting the SSL-certificates. Run:

sudo certbot --nginx

Since it’s the first time running it will ask for email and some other stuff.

The first setup of Certbot
The first setup of certbot

For the next step you can select specific domains, or all.

The first setup of Certbot
The first setup of certbot

It should create and set all certificates correctly. If in the future you add a new domain just rerun the command sudo certbot --nginx and select the new domains.

Auto renewal

Let’s Encrypt certificates are valid for 90 days, this is for security reasons. This does mean they need to get renewed periodically.
By default Certbot should have installed auto renewal. But we are going to check it with the command: systemctl list-timers --all

Out put of command systemctl list-timers --all
Checking Timers for certbot auto renewal

There should be some output about snap.certbot.renew.timer. It not we are going to setup the renewal ourself.

The command to renew the certificates is sudo certbot renew
You can also run this command by itself to test it out.

As it is run by root we will also add it to the crontab of the root user.

sudo crontab -e

Inside we add the following command.

0 0 * * 1 certbot renew

Here we set it to run every Monday at 0:00 (so Sunday to Monday night)
That’s more then enough, you can run it more if you want, I would not run it less.

Test your SSL connection.

Finally go to ssllabs.com and test your site. Behind the scenes Certbot also adds a bunch of security settings which will help get that A+.

SSLlabs results for janw.me
The results for janw.me on ssllabs.com

Installing WordPress Coding standards Globally

When possible I like to code using the WordPress coding Standards. I always set my PHPstorm coding style to WordPress. And when I start a new plugin I implement the PHPCodeSniffer rules for WordPress.
In the most ideal scenario you can include these in your composer settings, but that’s not always possible so it’s handy to have them installed globally.

To install run these 2 commands:

composer global require --dev squizlabs/php_codesniffer wp-coding-standards/wpcs phpcompatibility/phpcompatibility-wp dealerdirect/phpcodesniffer-composer-installer wptrt/wpthemereview
composer global update --dev --with-dependencies

Now all you need is installed. In your global composer directory. You can get that running: composer config home -g
By default is should return `~/.config/composer`

To test the phpcs with the global dir I run: ~/.config/composer/vendor/bin/phpcs -i
The output should look like this:

Result of command is "The installed coding standards are PSR12, PEAR, PSR2, PSR1, Squiz, MySource, Zend, PHPCompatibility, PHPCompatibilityParagonieRandomCompat, PHPCompatibilityParagonieSodiumCompat, PHPCompatibilityWP, WordPress-Docs, WordPress, WordPress-Extra, WordPress-Core and WPThemeReview"
Installed phpcs rulesets

Running this whole command will get annoying and will cluter up the terminal. So we will put the bin directory in the global $PATH
The bin directory we already got. (but we can also get using composer global config bin-dir --absolute)

To your ~/.bashrc add:

export PATH="$HOME/.config/composer/vendor/bin:$PATH"

Open a new terminal and test the command phpcs -i it should now work globally and is ready to run on individual projects.

Sources:

Install Ubuntu on Raspberry Pi (headless)

Let me state a few things before we start.
This guide will only cover the Ubuntu server part, so not the desktop. There are other guides that do cover that.
At the end of this guide you will have a headless Raspberry Pi running Ubuntu that you can login with ssh.

Download and Flash

From the official downloads page download the Raspberry Pi Imager. Install and run it. Select the latest Ubuntu version, select 64 bit for the Pi 3 or Pi 4.

The main screen of the Raspberry Pi Imager application
Raspberry Pi imager
Selecting Ubuntu using the Raspberry Pi Imager
Selecting Ubuntu using the Raspberry Pi Imager

After the SD card is done

When the writing of the SD card is done. Create a folder named ssh in the root of the system-boot partition.

If you need wifi I suggest this guide, I don’t use wifi on my Pi’s so I haven’t tried.

Then plug in the SD card and powerup. Do wait 2~5minutes before actually trying. The Raspberry has to setup stuff for the first time. If after 20max you still can’t find an IP. The flashing of the SD card has gone wrong and you’ll have to restart.

Finding the IP-address

First we need to get the IP-address that the Pi is running on. There are multiple ways to find the IP, you could login to your router and check for the device. It’s hostname is probably “ubuntu.home“, “ubuntu.local” or plain “ubuntu”. Usually the it will start with 192.168.x.x

There is an official guide for finding the IP-address. It’s one of the more annoying things when you are new. I myself use nmap

First login

Once you do have the IP-address login with the command ssh ubuntu@IP-HERE

Loggin in on Ubuntu for the first time
Loggin in on Ubuntu for the first time

The password is ubuntu
You will be asked to replace the default password with a new one. After changing it login again.

Default login for Ubuntu on Raspberry Pi
Default login for Ubuntu on Raspberry Pi

Now we can login we will change some things, some are optional.

Updates

For complete updates run: sudo apt update && sudo apt upgrade -y
This might take a while. When this is done for the first time I like to reboot, this is optional. sudo reboot now

Hostname

The default hostname is ubuntu, that might be fine but I’d rather give it a name that is more clear. Check the current hostname with the command hostname it should be ubuntu

For my example I will update the hostname to raspberry-webserver
Run the following command with the name you want.
sudo hostnamectl set-hostname raspberry-webserver

Then Reboot: sudo reboot now

Changing the hostname
Changing hostname

Static IP-address

Before we start a few notes. This part is always a bit annoying.

  • I followed 2 guides for this section, check them for more information.
    itzgeek & serverlab
  • If you can’t login after applying the changes pull the plug and stick the SD card in your computer and delete /etc/netplan/01-static-ip.yaml it can be found in the writeable partition.
  • My example only works when an ethernet cable is attached.
  • Depending on your router the 3rd part of the IP might differ, so it might be 192.168.1.161 it should be the same as the 3rd part of what you use to login originally.

Set the IP

The IP-address assigned is not guaranteed to stay the same. That depends on the router. When you reboot it might change. So I’d like to give it a static IP-address. In this example I will set the IP to 192.168.2.161

First we need the default IP of the router. We need it for the gateway. Run ip route show

Running ip route show
Default ip here is 192.168.2.254

This output shows “default via 192.168.2.254” That’s the IP we need.

We will create an extra netplan config file: sudo nano /etc/netplan/01-static-ip.yaml And match this content. Make sure you replace the IP with your own.
This is a yaml file so the number of spaces/tabs matters.

network:
  version: 2
  renderer: networkd
  ethernets:
    eth0:
      dhcp4: no
      addresses:
        - 192.168.2.161/24
      gateway4: 192.168.2.254

Next we reload netplan to apply the configuration: sudo netplan generate && sudo netplan apply This will likely freeze your terminal. Close it and login in a new terminal tab.

Done.

Your Pi is now up-to-date and ready for the real work.

RaspberryPi, Only allow ssh key login

This is the followup of Opening a Raspberry Pi to the outside world On the internet bots constantly try to hack servers. The Raspberry Pi is popular target. That’s why by default ssh is disabled nowadays.
To make this more secure we will only allow login by ssh key instead of a password. Ssh keys are a lot safer and you also won’t need to type/paste the password on login.

Open file: sudo nano /etc/ssh/sshd_config

Look for PasswordAuthentication
remove the # at the front and change the value to no.

PasswordAuthentication no

As a fall-back we will allow login in from the home network. So at the bottom of the file add. (the 4 spaces in front of the line matter)

Match address 192.168.*.*
    PasswordAuthentication yes

Restart the ssh service. sudo service ssh restart

ssh keys

Generate keys on the raspberry pi: ssh-keygen -t rsa -b 4096 -C "pi-webserver"
Just press enter for both the location and password.
Add your local key. Form your own machine mac/linux/windows.

Open File: nano ~/.ssh/authorized_keys

Paste your public key in this file.
For linux/mac you can find your public key with: cat ~/.ssh/id_rsa.pub

Always test in a new terminal tab/window.
That way if something goes wrong you’re still logged in in the previous tab.

You should login without needing to type the password. To check if the password is disabled you should try login in from a different computer and a different ip.

Raspberry Pi basic installation

This guide will help you set up a Raspberry Pi with Rasbian. Ready to do pretty much whatever you want.

What hardware do you need?

a Raspberry Pi, 4B a case, a network cable, power supply and an SD-card.
The Required Hardware
  • First off a Raspberry Pi of course, this tutorial uses a 4B 4GB. And should also work on a Model 2B and 3B.
  • Second I have a network cable. Wifi is available on since the 3B but wire always has a more stable connection, I will use a wire.
  • A USB-C power source. This can get a bit tricky. Recommended is at least 2A with 5V. A normal smartphone adapter won’t give that. Using less will slow down your Raspberry. If you’re using a 2B or 3B you need micro USB instead of USB-c
  • A micro SD card, at least class 10 for speed and with 8GB or more. The SD card will serve as the main harddrive for the pi. An micro SD adapter is also needed to install the image on the SD card.
  • A Case. With the Pi4 I’d recommend one with a builtin fan, as it can get to hot for itself.

Installing Rasberry Pi OS Lite

In the past this was called Rasbian. I liked that name much better….

From the official downloads page select the Raspberry Pi Imager for you OS. Download, install and open that. Select “Raspbberry Pi OS (other)” the one labeled Lite.

The main screen of the Raspberry Pi Imager application
Raspberry Pi imager
Selecting Raspberry Pi OS Lite with the Pi Imager
Select Raspberry Pi OS Lite
Progress of writing the image with the Raspberry Pi Imager
Almost done writing the image.

Flashing the image can take a while. You could set up all the wires and put things in place.

After the SD card is done

When the writing of the SD card is done. Create a folder named ssh in the root of the boot partition.

Also in the boot partition is a file called cmdline.txt. Open that, and at the end of the line add the IP you want to give the Pi. Usually you’re restricted to 192.168.1.x or 192.168.2.x but that depends on your router settings.

 EXTSTING_SETTINGS=SETTINGS ip=192.168.x.x

If you need wifi place a file named wpa_supplicant.conf also in the boot partition. And add the following:

network={
    ssid="YOUR_SSID_WIFI_NAME"
    psk="YOUR_PASSWORD"
    key_mgmt=WPA-PSK
}

I haven’t tried this myself so more information here, The static IP probably won’t work with wifi.

Then plug in the SD card and powerup. Do wait 2~5minutes before actually trying. The Raspberry has to setup stuff for the first time. If after 20max you still can’t find an IP. The flashing of the SD card has gone wrong and you’ll have to restart.

Go to your own computer and open the terminal (or Putty for the windows users) Login with the command ssh pi@192.168.*.* Use the IP address you noted before. If it asks if you want to continue choose yes. and use the default password: raspberry Putty again will work a bit different.

Login in on a Raspberry Pi by a terminal
Logging in for the first time.

Now you are logged-in remote on the raspberry pi. Time to configure some stuff with the command sudo raspi-config.
On this menu we can configure some basic settings. Let’s change some settings.

the start screen of raspi-config tool
Start screen of the raspi-config screen.
  1. Change User Password CHANGE THE PASSWORD. Longer is better and safer
  2. Network options
    N1 Hostname the name of your Raspberry in your network. I named mine pi-webserver. Not required. It’s just a label. But useful if you got multiple Raspberry’s running.
  3. Boot options
    B1 Desktop / CLI Chose the ‘Console Autologin’ or ‘Console’ option. Don’t choose auto login if someone else might have physical access to the pi. Desktop will just waste power.
    B2 Wait for Network at Boot Just turn this off to be safe.
  4. Localisation Options Here you can change the timezone to your current one. You could change the language of the PI and the keyboard layout if you need to. Keep in mind I will be using English.
  5. Interfacing Options we don’t need this
  6. Overclock Only for the Pi 2B, Set this to the highest setting. It won’t hurt and will make the Pi a faster.
  7. Advanced Options This will be a bit more.
    A1 Expend Filesystem Run this. No need to reboot immediately.
    A3 Memory Split Set this to 16. Because we don’t have a interface we won’t need memory for that.
  8. Update Run this is should not hurt. It’s only to update the raspi-config tool.

When this is finished the pi might download some language packs. It it doesn’t reboot. you have to do it yourself. sudo reboot now.

Wait about a minute and login in again like before. But with the new password

Updates

This might take a while but first we are going to update.

sudo apt update && sudo apt upgrade -y

Now we update all default installed software. If the image is old or hasn’t been updated in a while it might take a long time. The date of the image is in the file name of the downloaded image.
At times It might appear to hang. It Unless it’s stuck on one thing 15 minutes it’s probably fine. Give it an 15 to 30 minutes. Else pull the plug.

When it’s done reboot again just to be sure.

You now have a Raspberry Pi ready for use.

Shadow Posttype

The case I was dealing with. There is a posttype “person” and it had the normal templates, a archive-person.php and single-person.php with a bunch of metadata.
Which where visible on example.com/person/john-doe and the archive example.com/persons.
So far nothing special.

Every “person” was a -well- person. Some persons where also a “Judge”. A judge was an extra metabox on the person edit page.
And it needed it’s own pages on the frontend. example.com/judge/john-doe and example.com/judges.

Adding the rewrites

<?php
function judge_rewrite_rule() {
	/**
	 * Single judge
	 * 
	 * set is_judge to 1
	 * set person to post slug
	 */
	add_judge_rewrite_rule( '^judge/([^/]+)/?', 'index.php?is_judge=1&person=$matches[1]', 'top' );

	/**
	 * Archive for judges
	 */
	add_rewrite_rule( '^judges/?', 'index.php?is_judge=1&post_type=person', 'top' );
}
add_action( 'init','rewrite_rule' );

function judge_rewrite_tag() {
	/**
	 * make the query_vars aware of `is_judge`
	 */
	add_rewrite_tag( '%is_judge%', '([0-9]+)' );
}
add_action( 'init', 'judge_rewrite_tag' );

The main 2 functions to look out for are add_rewrite_rule and add_rewrite_tag.
I register the query_var is_judge and set it when the url is /judges/ or /judge/john-doe

After adding this do flush the permalinks. Just go to the settings for permalinks and press save. No need to change anything.

Registering the templates

<?php
/**
 * Set the correct theme/template-file.php
 * the shadow single & archive.
 *
 * @param string $template
 *
 * @return string
 */
function assign_judge_template( $template ) {
	if ( 1 !== (int) get_query_var( 'is_judge' ) ) {
		return $template; // we only check for templates when the judge set.
	}

	// assign the correct template.
	if ( is_single() ) {
		return get_template_directory() . '/single-person-judge.php';
	}
	if ( is_post_type_archive( 'person' ) ) {
		return get_template_directory() . '/archive-person-judge.php';
	}


	return $template; // fallback.
}
add_filter( 'template_include', 'assign_judge_template', 10, 1 );

When the is_judge is set set a different template. In this case this these templates are in the theme folder. You could also set templates from a plugin. The naming is can be anything but I picked single-person-judge.php so the next person can easier find the relation to the post type.

Setting the correct posts for the judge archive

Now the single will work as you would expect. But the archive page will still include all persons, not just the judges. Lets fix that.

<?php
/**
 * @param WP_Query $query
 */
function judge_pre_get_posts( $query ) {
	if ( 1 !== (int) $query->get( 'is_judge' ) || $query->is_single() ) {
		// only do this check if `is_judge` is set.
		// for single's this check is not needed.
		return;
	}
	// the meta_query
	$meta_is_judge = [
	    'relation' => 'AND',
		[ 'key'     => 'is_judge', 'compare' => 'EXISTS',],
		[
            'key'     => 'is_judge',
            'value'   => '1',
            'compare' => '=',
		],
	];

	// This part is to make sure you don't override other possible existing meta_queries
	$existing_meta   = $query->get( 'meta_query' );
	if ( empty( $existing_meta ) ) {
		$query->set( 'meta_query', $meta_is_judge );
	} else {
		$query->set( 'meta_query', [ 'relation' => 'AND', $existing_meta, $meta_is_judge, ] );
	}

}

add_action( 'pre_get_posts', 'judge_pre_get_posts' , 10, 1 );

If you’re familiar with the pre_get_posts hook this should not be to hard.
We select all posts that have the post_meta is_judge set to 1.
The way I add the meta_query seems a bit excessive, but this makes sure you never override a possible existing meta_query which an other filter might have added.

Now every Judge should be listed on example.com/judge and every individual judge should be visible on example.com/judge/john-doe. Only thing left to do is link to the page.

Linking to the judge url

As we want a person to be visible on both example.com/person/john-doe and example.com/judge/john-doe we can’t override the default permalink. So the best next thing is to create a helper function for calling the judge link.
This function should be used where you need to link to example.com/judge/john-doe, like on the judge archive page.

<?php
/**
 * Helper function
 *
 * @param int|WP_Post|null $post
 *
 * @return string, if not a judge, return normal permalink
 */
function get_judge_permalink( $post = null ) {
	$post = get_post( $post );
	if ( is_null( $post ) ) {
		return null;
	}

	// if the meta `is_judge` is not set, return the default person permalink.
	// for your use cases you might want to return something different.
	if ( '1' !== get_post_meta($post->ID, 'is_judge', true ) ) {
		return get_the_permalink( $post );
	}

	return home_url( 'judge/' . $post->post_name );
}

The function can be used in the same way as the regular get_the_permalink. get_judge_permalink(); the post argument is optional, and can also be a post_id.

Closing thoughts

In the template files, I suggest adding a very clear comment at the top, which explains where the rewrite functions are. An other person will otherwise have a hard time to find where this is created.

The list of judges will now automatically appear on the /judge/ archive page. If you need to create this list on a custom WP_query just add the query_var is_judge, example:

<?php
$q_args = array(  
	'post_type' => 'person',
	'is_judge' => 1, // this will get picked up by the `judge_pre_get_posts` filter
	// other arguments
);
$custom_query = new WP_Query($q_args);
// do what you want

Install WordPress on a Raspberry Pi

WordPress the CMS that doesn’t need introduction.
In this guide we are going to install it and configure it a bit.

It is required you took the previous steps of setting up the Raspberry, installed php, nginx and mysql.

Setup Url

So far we’ve setup example.local we will create a second url and add configure it. I will use wordpress.local but feel free to use whatever you want.

First lets setup the correct directories.

sudo mkdir -p /var/www/wordpress.local/public_html
sudo chown www-data:www-data /var/www/wordpress.local -R
sudo nginx -t && sudo service nginx reload

Create the vhost and fill it with with the following.
sudo nano /etc/nginx/sites-enabled/wordpress.local.conf

server {
    listen 80;
    
    ## Your website name goes here.
    server_name wordpress.local www.wordpress.local;
    ## Your only path reference.
    root /var/www/wordpress.local/public_html;
    ## This should be in your http block and if it is, it's not needed here.
    index index.php;

    location = /favicon.ico {
            log_not_found off;
            access_log off;
    }

    location = /robots.txt {
            allow all;
            log_not_found off;
            access_log off;
    }

    location / {
            # This is cool because no php is touched for static content.
            # include the "?$args" part so non-default permalinks doesn't break when using query string
            try_files $uri $uri/ /index.php?$args;
    }

    location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ {
            expires max;
            log_not_found off;
    }
    
    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
        fastcgi_intercept_errors on;
        fastcgi_buffers 16 16k;
        fastcgi_buffer_size 32k;
    }
}

This config is the bare minimum as recommended by the nginx manual.
It works but it is not the best. The official WordPress docs has some more examples.
The main thing to improve is security.
I might do a better config in the future but for now this is all.

For the final step reload nginx.

sudo nginx -t && sudo service nginx reload

Create DB

Login to the mysql prompt:

sudo mysql -uroot

After that run the following lines to create a database, create a user with a strong password and assign permissions.

CREATE DATABASE raspimain_db;
CREATE USER 'raspimain_user'@'localhost' IDENTIFIED BY 'P@SSW0RD_Str0nG_R@nD0m_&_L0ng';
GRANT ALL PRIVILEGES ON `raspimain_db`.* TO `raspimain_user`@`localhost`;
FLUSH PRIVILEGES;
EXIT;

Download WordPress

Go to the new WordPress directory.

cd /var/www/wordpress.local/public_html/

Download the latest version of WordPress, and unzip it.

sudo wget https://wordpress.org/latest.zip
sudo unzip latest.zip
sudo mv wordpress/* ./
sudo rm -r wordpress latest.zip
sudo chown www-data:www-data . -R

Install WordPress

Now go to the url and just follow the famous 5 minute install.
And that is all.
A few things to check after the install is done.

Gravity Forms give editors access

By default Gravity forms isn’t accessible by editors. There are ways to do this with plugins like members.
As some might guess I prefer wp-cli for this:

wp cap add editor gravityforms_create_form gravityforms_edit_forms gravityforms_view_entries gravityforms_export_entries gravityforms_delete_entries graviyforms_delete_forms gravityforms_edit_entries gravityforms_view_entry_notes gravityforms_edit_entry_notes --grant
Difference in rights after setting capabilities

Running this will pretty much give all rights except the settings of Gravifty Forms.
It will allow creatign, editing & deleting forms. But also view entries and export them.

Full list of Gravity Forms Capabilities

Nano Shortcuts

I’ve know about nano and some it’s shortcuts. Today I explored them a bit more deeply.
So a list of shortcuts I find useful:

  • ctrl+K Cut the current line and put it in the nano clipboard (it’s not the same as the general clipboard)
  • ctrl+U Paste the line
  • ctrl+W Open search, type and hit enter. For the next match press alt+W
  • ctrl+Q Search backwards. For the next backward match press alt+Q
  • alt+U Undo
  • alt-E Redo
  • alt+C show the line number
  • alt-G Go to line number

Sources:

  • https://www.nano-editor.org/dist/latest/cheatsheet.html
  • https://askubuntu.com/a/672091/208343