SSH Key Pair Authentication Setup

Ever wanted to connect to a remote server or host and tired of typing a password and worrying about password attacks. The solution is to use SSH key pairs, we will be covering SSH key pair authentication setup.

In addition, passwords shouldn’t be used in today production environments as they are typically weak in comparison with a public and private key pair. Multi-factor authentication should also be used, however this post will not be covering this setup.

SSH Key Pair Generation

To generate these keys, you will use ssh-keygen. Do not use RSA or DSA, they are weak, the strongest encryption key out at the moment is ed25519.

Use command:

ssh-keygen -a <large integer value> -t ed25519 -f ~/.ssh/id_ed25519 -C "<Username/Comment>"

For Example:

ssh-keygen -a 10000 -t ed25519 -f ~/.ssh/id_ed25519 -C “Nate15329”

If you use a long and strong passphrase, this will avoid from someone using these keys if they ever got a copy. You will need to type in the passphrase like a password when remoting in, but also the SSH key is also validated.

Remote System Setup

Next is preparing and copying the public key to the remote system that is being accessed. Never copy the private key to another system. Public keys end with .pub extension and only these are to be copied to remote systems.

Ensure the remote system has the following folder structure and file preexisting for the account. Below are the commands to get these setup.

mkdir ~/.ssh
touch ~/.ssh/authorized_keys
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys

Copy the key to remote system via:

ssh-copy-id <user>@<server name/ip address>

or from Windows Powershell to new remote system:

scp $env:Userprofile\.ssh\ <user>@<server name/ip address>:~/.ssh/authorized_keys

or from Windows PowerShell to remote system with existing ssh authorized keys:

type $env:USERPROFILE\.ssh\ | ssh <user>@<server name/ip address> "cat >> .ssh/authorized_keys"

Remote System SSH Lockdown

To prevent SSH daemon from using passwords as a secondary authentication method, please do the following.

  1. Open /etc/ssh/sshd_config with your favorite file editor as sudo or root user.
    • Note: We do not endorse the usage of logging in as root as mistakes may happen and things will break, along with a high security risk.
  2. Find PasswordAuthentication and set it to no (PasswordAuthentication no)
  3. Find ChallengeResponseAuthentication and set it to no (ChallengeResponse Authentication no)
  4. Save and close the file.
  5. Restart your sshd daemon, usually by sudo service sshd restart or sudo systemctl reload sshd

Congrats, now your system should only accept SSH key authentication.

Log2RAM – SD Card Savior

While setting up our Raspberry Pis, one of the things that we usually setup is Log2Ram. The entire goal of this project is to extend the SD storage life.

By default Raspberry Pi stores logs on the SD card, however SD cards have limited write cycles due to being FLASH based storage. Once this limit has been reached, it will fail. Log2Ram forces logs to be stored in memory (RAM) by changing /var/log to a RAM disk and once every day to reduce writing and increase lifespan of the SD card.

Highly recommend for any environment running off of an SD card, along with configuration and sending logs to a central logging system such as syslog. We won’t be covering the central logging, but recommended for production environments.

Installation is pretty straight forward.

git clone
cd log2ram
chmod +x
sudo ./

Usually I edit /etc/log2ram.conf to set size to 128 MB instead of default 40MB by using sudo nano /etc/log2ram.conf


Then a restart would be need to be done to make this become in effect by sudo reboot

With future updates, I suggest following the advice that is in the README file. Usually, we would need to manually stop the service prior to running the update.

Ender 3 – MKS Gen L – Marlin Upgrade

We had upgraded our Ender 3 to Marlin, this will provide the steps that we did. As noted, we are using the MKS Gen L board and not the board the Ender 3 comes with.

With the new Marlin firmware, it is better in our opinion to compile it via PlatformIO in Visual Studio Code than in the Arduino IDE. It is recommended to record any values stored such as current z-offset, extrusion settings, etc before flashing the updated firmware as these values will need to be placed back afterwards.

Environment Setup

Please follow these steps to setup the environment:

  1. Install Microsoft’s Visual Studio Code from
  2. Install the following extensions: PlatformIO (by PlatformIO) & Auto Build Marlin (by Marlin Firmware)
  3. Relaunch Visual Studio Code.
  4. Download Marlin Firmware Version from and extract the zip folder.
  5. Download the Ender-3 configuration from the Github repo as well: Link


Let’s get started with configuring the project:

  1. Place the Ender 3 example config files (configuration.h and configuration_adv.h, optional _Bootscreen.h and _Statusscreen.h) that was downloaded under step 4 under Marlin/src directory.
  2. Click on the PlatformIO module in Visual Studio Code, click open project and select the directory where the platformio.ini file is located
  3. Modify the configuration.h as follows:
    1. Remove #define CONFIG_EXAMPLES_DIR "Creality/Ender-3/CrealityV1" line, it may have a different path
    2. If the _Bootscreen.h and/or _Statusscreen.h has been copied; either to un-comment or comment (//) the lines beginning with associated lines beginning with #define SHOW_CUSTOM_BOOTSCREEN and/or #define CUSTOM_STATUS_SCREEN_IMAGE
    4. Change the stepper directions to opposite; end result values should be: #define INVERT_X_DIR false , #define INVERT_Y_DIR false ,& #define INVERT_Z_DIR true
    5. Change the extruder direction to the opposite; end result value should be #define INVERT_E0_DIR false
    6. Make any additional changes you may need for your setup.
  4. Modify the Configuration_adv.h as below:
    1. Set the line #define E0_AUTO_FAN_PIN to #define E0_AUTO_FAN_PIN 7

Building the firmware

To build the firmware, the Marlin Auto Build extension will be used in Visual Studio Code:

  1. On the Marlin Firmware Auto Build extension, click Refresh button. The values will be updated to what is in the configuration files.
  2. Click on the Build button next to mega2560 ; this should give a succeeded message in the terminal output. If it doesn’t give a success message, please review the configuration files for configuration issues.
  3. On successful build, plug in the MKS Gen L board via USB and press the Upload button.

After the upload, don’t forget to place back any custom values prior.

A good practice is to archive this firmware somewhere (plus a simple date stamp) where it can be used as a future reference to newer Marlin firmware versions.

UniFi Controller – Self Hosted Docker

One of the wifi access point brands that we’ve always recommend to family, friends, and others is Unifi. Usually, we end up having to do maintenance of their equipment. A solution is to setup an Unifi Docker container for a L3 management over the internet. Sure, we could set it up to phone home to UniFi cloud management, but what would be the fun of that?

We would like to note that we are not Docker experts or work with Docker on a daily basis and it’s something we would like to study more into when we have the chance. We are sure there is a more automated way to maintain the Docker image.

Step 1: On a Linux host, add a service account for Docker. Doesn’t need to be the same name. After adding the user, get the UID of the user, this will be referenced later.
sudo adduser docker_unifi

Step 2: Install Docker
curl -fsSL -o

Step 3: Setup persistent storage.
sudo mkdir -p /var/docker_storage/unifi
sudo chown docker_unifi:docker_unifi /var/docker_storage/unifi

Step 4: Pull and create Docker Container

I’ll be using Jacob Albery’s container for Unifi. Use a container that you trust, at this time of this post, there was no official Unifi container.
docker pull jacobalberty/unifi:stable

To create the container, we will need the UID and GID of the non root user you’ve created in Step 1.
id -u docker_unifi
id -g docker_unifi

In this case, the UID and GID of docker_unifi is 1001.

The below command will create the Docker container and allow it to automatically restart if it ever crash and will not run as root. In this case, we are using L2 discovery to setup and firmware upgrade the APs on the local network. Otherwise, we would use network port forwarding.
docker run -d --restart=always --net=host --name=unifi -e RUNAS_UID0=false -e UNIFI_UID=1001 -e UNIFI_GID=1001 -v /var/docker_storage/unifi:/unifi jacobalberty/unifi:stable

To update this container in the future, use the following commands. Just be sure to remember what you used for creating the Docker container.

docker pull jacobalberty/unifi:stable
docker stop unifi
docker rename unifi unifi.old
docker run -d --restart=always --net=host --name=unifi -e RUNAS_UID0=false -e UNIFI_UID=1001 -e UNIFI_GID=1001 -v /var/docker_storage/unifi:/unifi jacobalberty/unifi:stable

If the upgrade was successful, remove the old container by using command:
docker rm unifi.old

Next is setting up external DNS.

External DNS – Setup

Since normal consumer external IP addresses usually change every so often and we needed to allow the firewall to only allow certain IP addresses as a source. An A or AAAA record will be needed to be updated dynamically by a client at the client’s location.

Luckily, we are able to setup the DDNS settings at these locations, each with their own unique API key following a naming convention and under a different domain name than the primary one utilized. If the client has a public domain name already, see if you can get them to setup DDNS on public DNS instead. We are using Cloudflare DNS nameservers and protections; this will be different depending on what you use.

We suggest making a subdomain that you only know and add sub-records to that.

For example:

  • A record for 1 client site
    • Host Name: c1.u.i
    • Proxy status: DNS only (do not use proxied, your firewall will block these)
  • A record for another client site
    • Host Name: c2.u.i
    • Proxy status: DNS only

If DDNS is configured correctly, the DNS records update with the correct IP addresses. Next we configure the firewall to only allow these certain FQDN.

Firewall – Setup

We use pfSense firewall here and the easiest way to setup the firewall settings and not having multiple repeating rules is to setup an Firewall Alias for both the Ports and Hosts allowed. Below is an example of the Hosts alias.

In the firewall rules, we set the Hosts Alias as the source, the destination, and the Port Alias for the allowed custom Ports. This would prevent usual internet background scans and malicious users/bots from seeing the ports are open.

As always, secure this environment with firewalls and within a DMZ network zone that is separate from your normal network and always keep this host and the Docker image updated. We allowed the following ports through the firewall defined here:

UniFi Controller – Setup

Be sure to set the Controller hostname to the FQDN and check the box to Override inform host with Controller Hostname/IP. Also, update this public DNS record to point to the Controller WAN IP address.

To remove/migrate a external client

  1. Migrate their Unifi Management 1st. Note: Site Export works wonders.
  2. Notify the DDNS record is no longer required (unless they set it up and want to keep it)
  3. If any, remove their public DNS entries & their DNS API access to your domain management
  4. Remove the FQDN name from the firewall host alias.