- Last Update:

Raspberry Pi: The Ultimate Guide

A complete tutorial about how to create a home server using a Raspberry Pi

🕒 28 min read

Category: Linux

Tags: raspberry pi, linux, talk

Here is an old document I created a while ago. I keep it here for legacy purposes.

I'll keep this article as short as possible (no explanation where things are self-explanatory or obvious), mainly due to the fact that the article will be quite lengthy. I'll give instructions about how to setup a Raspberry Pi as well as how to install stuff. This blog post might change in a near future.


I decided to built a cheap home server to host websites, have a VPN server hosted in France, have a Nextcloud instance, and so forth. A Raspberry Pi, which costs less than $5 a year (electricity consumption) was the perfect solution. As I'm not that often in France, I needed to be able to operate it remotely. I also wanted plenty of storage, a SD card would not be sufficient. I had to plug a hard disk drive. Furthermore, I wanted it to be encrypted, just in case a malicious person tries to read my hard disk drive.

To sum up, few requirements, but big advantages. Let's get started!

What I used for this tutorial

Setting up the Raspberry Pi

The OS

  1. Download Raspbian Lite from the official website. It might be a good idea to verify the hash (SHA1).
  2. Extract the file .img from the zip.
  3. Run one of the following commands:

    fdisk -l
  4. Plug in the SD card (on a regular computer, not the Raspberry Pi)

  5. Redo step 3. in order to identify the SD card. /dev/mmcblk0 or /dev/sdb for instance. /dev/mmcblk0pX or /dev/sdbX would be a partition on the device, with X an integer.
  6. Unmount all the partitions and copy Raspbian on the whole SD card:

    umount /dev/mmcblk0p1
    sudo dd bs=4M if=2014-09-09-wheezy-raspbian.img of=/dev/mmcblk0 status=progress conv=fsync
    sudo sync && sync

    bs=1M can be used in case of error, it's slower but safer. There won't be any feedback during dd so wait till it finishes (might be long).

  7. You're done. For more information, see the official website. Now enable SSH by creating an empty file named ssh in /boot. Then, unmount the SD card and eject it.

First boot

  1. Insert the micro SD card in the Raspberry Pi. Plug in an Ethernet wire or make sure you have a Wifi access point available on which you can connect, Plug in the HDMI cable (connected to a screen), a keyboard and eventually the power cable.
  2. Log in (user name is pi and password is raspberry). You can do it using a keyboard or over SSH if you Raspberry is connected. Then set the locales and the right keyboard layout:

    sudo su
    dpkg-reconfigure locales # Select with space bar, at least en_US.UTF-8 plus any other you need
    dpkg-reconfigure keyboard-configuration # A keyboard must be plugged in
    dpkg-reconfigure tzdata

    Regarding the keyboard layout, there are other alternatives. Then reboot (sudo reboot).


  1. Should you need to connect over Wifi, here's how to do it:

    sudo iwlist wlan0 scan
    sudo nano /etc/wpa_supplicant/wpa_supplicant.conf

    Go to the bottom of the file and add the following:


    Otherwise, disable Wifi (and Bluetooth) as it draws power. Create /etc/modprobe.d/disable_rpi3_wifi_bt.conf and add the following:

    blacklist brcmfmac
    blacklist brcmutil
    blacklist btbcm
    blacklist hci_uart

    Save and reboot. More information. At next reboot, make sure you're either connected or that Wifi is disabled by typing ip a.

  2. Double the current limit if your intend to plug USB devices. In /boot/config.txt, add the following:


    Some people say this has no effect on Raspberry Pi 3.
    You may finish editing this file by fine-tuning its parameters.

  3. Update you Pi:

    sudo apt update
    sudo apt upgrade
    # sudo apt install rpi-update # if this packet is missing
    # sudo reboot
    # sudo rpi-update # gets the latest firmware, normal users should not have to do it
    # More info, read: https://github.com/Hexxeh/rpi-update#notes
    # sudo reboot
    # sudo apt update
    # sudo apt upgrade
  4. For security concerns, let's create a new user. The pi user will be deleted later.

    sudo su
    passwd # Add a password to the root account
    adduser pipi
    usermod -a -G video pipi # Otherwise omxplayer won't work with this user
    echo 'export HISTSIZE=100000' >> /root/.bashrc
    echo 'export HISTFILESIZE=100000' >> /root/.bashrc
    # Stay connected as 'pi' for now
  5. Config the Pi:

    sudo raspi-config

    You should not expand the filesystem, neither change the user password cause we'll delete the pi user anyway. However, you should change the boot options (select choice B2) and the internationalisation options to English (UTF-8) (same for the environment locale, do not use C.UTF-8). If you want to change the timezone and keyboard layout, plug in a keyboard on the Pi and do it from that keyboard. Change overscan if you see black bars. Change the hostname if you want. Finally, adjust memory split (128MB is sufficient for 1080p videos). Then, reboot. And log in back using the pi user.

  6. You should probably remove the ability to run "root" commands without typing pi’s password.

    sudo visudo # it will safely edit /etc/sudoers

    Here, remove "NOPASSWD: ". If there is no such a line, check out the file /etc/sudoers.d/010_pi-nopasswd and delete it.

  7. From another computer (not your Pi), do this:

    ssh-keygen # Use default choices
    ssh-copy-id -i $HOME/.ssh/id_rsa.pub pipi@<raspberry-pi-IP>
  8. Get back to your Pi, still logged in as pi and do:

    apt install ntpdate rsync
    # ntpdate to set the clock correctly.
    # rsync is just better than cp
    ntpdate -u fr.pool.ntp.org
  9. Now, it's time to delete the pi user. Log in as your new user (in my case it's pipi) and then:

    su # Type the root password here
    deluser --remove-home pi 
    visudo # Make sure there's no reference to pi user
  10. Finally, make sure to copy these three lines in both your <new home>/.bashrc and /root/.bashrc:

    export LC_ALL="en_US.UTF-8"
    export LANG="en_US.UTF-8"
    export LANGUAGE="en_US.UTF-8"

    And then run the following commands, as super-user:

    source .bashrc && locale-gen "en_US.UTF-8" && dpkg-reconfigure locales

Moving /root onto an external hard disk drive, not encrypted, with additional encrypted DATA partition


Right after this section you will find another one about how to do a similar operation using an encrypted hard disk.

As root,

lsblk # Identify the hard disk drive connected to the Pi
fdisk /dev/sda

Do the following sequence of keystrokes:

d # Delete all the existing partitions if many
n # Create a new one
p # Primary
<Enter> # Hit enter key
+100G # 100GB, or something else
n # Create a second partition, swap
t # Change the type...
2 # Of the second partion
82 # Make it a swap partition
n # Create the data partition
p # Make sure everything is OK


mkfs.ext4 /dev/sda1 -L ROOT -m 5
mkswap /dev/sda2
mkfs.ext4 /dev/sda3 -L DATA -m 5
apt install cryptsetup
cryptsetup --verify-passphrase -c aes-xts-plain64 -s 512 -h sha256 luksFormat /dev/sda3
cryptsetup -v luksOpen /dev/sda3 hddcrypt
mkfs.ext4 /dev/mapper/hddcrypt -L DATA -m 1
mkdir /mnt/data_partition
mount /dev/mapper/hddcrypt /mnt/data_partition/

cat /boot/cmdline.txt # See if you can find root=/dev/mmcblk0p2. If yes then...
sed -e "s|root=/dev/mmcblk0p2|root=/dev/sda1|" -i /boot/cmdline.txt
# Otherwise, manually replace root=PARTUUID=123 with the right PARTUUID found in `blkid`
# Add 'bootwait' at the end of the line, in the same file
# The 'rootwait' option forces the kernel to wait until the root device becomes ready

Edit /etc/fstab that way:

proc            /proc           proc    defaults          0       0
/dev/mmcblk0p1  /boot           vfat    defaults          0       2
/dev/sda1       /               ext4    defaults,noatime  0       1
/dev/sda2       none            swap    sw                0       0


mkdir /tmp/rootsd && mount /dev/mmcblk0p2 /tmp/rootsd
mkdir /tmp/roothdd && mount /dev/sda1 /tmp/roothdd
rsync -a /tmp/rootsd/ /tmp/roothdd/
e2fsck -f /dev/sda1 # You might need to umount it first
update-rc.d -f dphys-swapfile remove
apt remove dphys-swapfile
cat /proc/swaps
swapoff /var/swap # Might fail if it was not listed in the previous command
sudo rm -f /var/swap
/sbin/swapon -s # Make sure only your swap partition is in use
dd if=/dev/urandom of=/dev/mmcblk0p2 bs=10M # Delete unused old root partition
fdisk /dev/mmcblk0 # Delete second partition

Moving /root onto an external hard disk drive, encrypted

I would like to thank a few websites which helped me a lot to write this article:

Stay logged in as root.

echo "initramfs initramfs.gz 0x00f00000" >> /boot/config.txt
cat /boot/config.txt

cat /boot/cmdline.txt
sed -e "s|root=/dev/mmcblk0p2|root=/dev/mapper/hddcrypt cryptdevice=/dev/sda1:hddcrypt|" -i /boot/cmdline.txt
cat /boot/cmdline.txt # Check it has been effectively changed

sed -e "s|/dev/mmcblk0p2|/dev/mapper/hddcrypt|" -i /etc/fstab
cat /etc/fstab # Make sure it's all right
echo -e "hddcrypt\t/dev/sda1\tnone\tluks" >> /etc/crypttab
cat /etc/crypttab

lsblk # Identify the hard disk drive connected to the Pi
fdisk /dev/sda # Delete any existing partition, create new one with default choices
apt install cryptsetup
cryptsetup --verify-passphrase -c aes-xts-plain64 -s 512 -h sha256 luksFormat /dev/sda1
# 512-bit AES encryption with 256-bit SHA hashing algorithm
cryptsetup -v luksOpen /dev/sda1 hddcrypt
mkfs.ext4 /dev/mapper/hddcrypt -L ROOT_HDD -m 1

# Let's mount in two directories the unencrypted root partition from the SD card
# and the new encrypted root partition from the HDD
mkdir /tmp/rootplain && mount /dev/mmcblk0p2 /tmp/rootplain/
mkdir /tmp/rootcrypt && mount /dev/mapper/hddcrypt /tmp/rootcrypt/
rsync -a /tmp/rootplain/ /tmp/rootcrypt/ # Copy from plain to encrypted

mkinitramfs -o /boot/initramfs.gz $(uname -r)

# Delete old /root
dd if=/dev/urandom of=/dev/mmcblk0p2 bs=10M status=progress conv=fsync
fdisk /dev/mmcblk0 # Delete second partition

Enable remote unlocking


Let's get started! On your Pi:

apt install dropbear
mkinitramfs -o /boot/initramfs.gz $(uname -r) # Triggers SSH key generation

# Next command is really important, it gives the Pi enough time to get an IP address
sed "s/configure_networking\ \&/echo \"Waiting 5secs...\"\nsleep\ 5\nconfigure_networking\/" -i /usr/share/initramfs-tools/scripts/init-premount/dropbear
cat /usr/share/initramfs-tools/scripts/init-premount/dropbear

cd /etc/dropbear/
rm /etc/initramfs-tools/etc/dropbear/dropbear_dss_host_key
rm /etc/initramfs-tools/etc/dropbear/dropbear_rsa_host_key
# Next command improves security. Default length is 1024
dropbearkey -t rsa -s 4096 -f /etc/initramfs-tools/etc/dropbear/dropbear_rsa_host_key

Now, add the following at the beginning of the first line of the file /etc/initramfs-tools/root/.ssh/authorized_keys:

command="/scripts/local-top/cryptroot && kill -9 `ps | grep -m 1 'cryptroot' | cut -d ' ' -f 3`"

To forbid authentication using passwords and to change the listening port used by Dropbear, create the file /etc/initramfs-tools/conf.d/dropbear and add:

export PKGOPTION_dropbear_OPTION="-s -p 1234"

And do:

mkinitramfs -o /boot/initramfs.gz $(uname -r) # Might be useless
update-initramfs -u

From now, you have 3 possibilities to access your Raspberry Pi remotely:

  1. Using a public key
  2. Using the Raspberry's private key
  3. Using an account's password

The two first possibilities are quite safe, compared to the third one. It's well known that authentication using passwords over SSH is not that safe.
Using your PC's SSH public key is a good solution but not reliable in the long term. Imagine you were to lose access to your personal computer, you would end up locked out of your Raspberry with no means to access it. On the other hand, using your Raspberry Pi's SSH private as an identity is far more convenient in that you can store that file on many devices. Should you lose you computer, you could still access your Raspberry from another computer, given that you still have access to that private key. I know it's not the safest solution but that's the one I chose to go with.

So here are how to do it with those two first choices, but I recommend you to go with the second one.

Choice 1

echo $(cat /home/pipi/.ssh/authorized_keys) >> /etc/initramfs-tools/root/.ssh/authorized_keys # Will grant us direct SSH access
cat /etc/initramfs-tools/root/.ssh/authorized_keys # Make sure it's all right

Choice 2

Save the content of /etc/initramfs-tools/root/.ssh/id_rsa on your computer. Make sure to copy the entire file, including first and last lines. And do that on the newly created file, on your computer:

chmod 0600 id_rsa_rpi

Now, you can connect to your Pi like this:

ssh -i id_rsa_rpi root@192.168.1.XX -v -p 1234

Finally, last step:

update-rc.d dropbear disable # Only openssh will be used after partition is decrypted
mkinitramfs -o /boot/initramfs.gz $(uname -r) # Might be useless
update-initramfs -u

For more information in this whole thing, read the official documentation provided by cryptsetup:

zcat /usr/share/doc/cryptsetup/README.remote.gz

Should you need to upgrade your firmware, make sure to update the right initramfs before rebooting. Read the end of this article to find out how to do it.

Instead of unlocking your hard disk drive over SSH, if you prefer to use a keyfile, check out this website.

If you are brave enough, have a look at the section below called Hardening security. You might want to improve Dropbear settings.

You might also want to read about Mozilla's recommandation in terms of SSH security.


Screen blank time

To avoid screen blanking after a while in the console (tty), change BLANK_TIME to 0 in /etc/kbd/config.

Auto login

As previously seen, the tool rasp-config allows you to configure auto login. But you can also do this with the command line. In /etc/inittab, replace

1:2345:respawn:/sbin/getty 115200 tty1


1:2345:respawn:/bin/login -f pi tty1 </dev/tty1 >/dev/tty1 2>&1

Stopping Raspberry-Pi from auto changing source on TV

sudo echo "hdmi_ignore_cec_init=1" >> /boot/config.txt

Auto-mount an external hard disk drive

mkdir /home/pi/my_wonderful_hdd # Where the HDD will be mounted
sudo fdisk -l # Note the location of the HDD (something like /dev/sda1)
sudo mount -t auto /dev/sda1 /home/pi/my_wonderful_hdd # Mount it now
sudo echo "/dev/sda1 /home/pi/my_wonderful_hdd auto noatime 0 0" >> /etc/fstab # Auto mount at boot

Change hostname

Use the tool rasp-config or edit both /etc/hostname and /etc/hosts.

Disable SWAP permanently

Swapping is bad for your SD card lifespan. You should disable it permanently. You might however want to keep swap on a partition on a hard disk drive (see above). To disable permatently on SD card and disk, run:

sudo swapoff --all # Temporary, disables also dedicated partitions (like /dev/sda2)
sudo update-rc.d dphys-swapfile remove
sudo apt remove dphys-swapfile # Permanently
sudo rm /var/swap


  1. apt install lynx (as root)
  2. Download https://github.com/rpellerin/dotfiles.
  3. Copy in your user's home folder the directory scripts/DynHost.
  4. Edit the lines HOST, LOGIN, PASSWORD and PATH_APP in the file dynhost.
  5. Add a cronjob (crontab -e), on your Pi, with the same user (not root!):

    */1 * * * * /home/pi/DynHost/dynhost

    All good!

Send email automatically on startup with a Python script (DEPRECATED: not recommended, instead use sendmail)

  1. Download https://github.com/rpellerin/python-mailer. The instructions given right below are the same as those you'll find at this URL.
  2. Install Python 3 (see instructions in https://github.com/rpellerin/dotfiles/blob/master/README.md for the latest version or simply do apt install python3 as root).
  3. Do, as a normal user (not root!):

    mv /path-to-python-mailer/scripts/script-pc-turned-on.py /path-to-python-mailer/script-pc-turned-on.py
    echo '#!/bin/sh' > $HOME/sendEmailOnStartup.sh
    echo "python3 /path-to-python-mailer/script-pc-turned-on.py" >> $HOME/sendEmailOnStartup.sh
  4. Edit, in script-pc-turned-on.py, the path to the file containing recipients.

  5. Create that file and add email addresses in the following format:

    John Doe,john@doe.com
  6. Edit the file config.py.

  7. Do:

    chmod +x $HOME/sendEmailOnStartup.sh
  8. Try to execute that file, as a normal user (not root!), to make sure everything is all right:

  9. If OK, add the following in /etc/rc.local, right above exit 0:

    su - pi -c /home/pi/sendEmailOnStartup.sh &
    sleep 5
    exit 0

    Don't forget to change the path if need be. You user might not be pi. Reboot to make sure it worked.

Send email automatically on startup with sendmail

Add the following in crontab -e as root:

@reboot /bin/sleep 30; /usr/sbin/exim -qff; echo "So you know... ($(date))" | mail -s "Rpi turned on" me@domain

Now, read the section right below.

Configure a local SMTP email server

Before doing this. make sure you did set the hostname of your Raspberry Pi through raspi-config. Note: to successfully send email, the domain you set must exist as a valid DNS entry. Otherwise some email servers will reject your emails. If your Raspberry won't answer to no domain, let as is but make sure while setting up exim4 to hide local mail name with an existing domain.

Make sure to disable /var/log from being in RAM since Exim4 needs /var/log/exim4/mainlog to exist, even after a reboot.

This is pretty convient as some programs still prefer to send emails, such as cron.
Normally, Exim4 comes pre-installed with Debian. If not, do apt install exim4. Then:

dpkg-reconfigure exim4-config
# Second choice, "mail sent by smarthost; received via SMTP or fetchmail"
# System mail name: keep default; must be a valid FQDN though (ending with .com for example). Leaving blank is the same as reusing the same hostname you set for the Raspberry but it is sometimes buggy. Better to explicitely write your hostname.
# IP-addresses to listen on: keep default, we don't want to receive external emails
# Other destinations: leave blank
# Machines to relay mail for: leave blank
# IP address or host name of the outgoing smarthost: your SMTP server with port, like ssl0.ovh.net::465
# Hide local mail name: no, or yes if you let 'System mail name' blank. If you set a domain, it MUST EXIST otherwise some server will reject your emails.
# Keep number of DNS-queries minimal: no
# Delivery method for local mail: mbox
# Split configuration into small files: no
# Root and postmaster mail recipient: write one of your email addresses or leave blank

Now update-exim4.conf.

Then, cat /etc/mailname and make sure the system mail name you just specified is correctly reported here.

In /etc/exim4/passwd.client, add you credentials, like:


If your SMTP server uses port 465 with SSL, you'll need to edit /etc/exim4/exim4.conf.template. Add the following line, after driver = smtp, under remote_smtp_smarthost:

protocol = smtps

More information.

Now add these lines in /etc/aliases (changes the lines according to your needs):

root: user1
user1: address1@domain
user2: address2@domain

Any email intended for user1 will be sent to the corresponding email address. Do not add addresses using the same domain you chose while configuring exim4 as the emails won't be sent out.
You can also edit /etc/email-addresses: this file contains addresses used for from, reply-to and sender addresses fields.


newaliases # To apply changes brought to aliases
# /etc/init.d/exim4 restart
systemctl restart exim4
echo "This is a test." | mail -s "test message" anotherme@somewhere.com # Try sending an email
echo "This is a test." | mail -s "test message for root" root # Try sending an email
runq; exim -qff # Flush all email queues

More information.

Send emails automatically on shell login

Edit your user and root's .bashrc and add at the end of the file:

echo "So you know... ($(date))" | mail -s "Root shell login" me@domain

Installing Nextcloud 15.0.0

Official tutorial: https://docs.nextcloud.com/server/15/admin_manual/installation/source_installation.html.

apt install apache2
apt install mariadb-server
apt install libapache2-mod-php7.0 php7.0
apt install php7.0-gd php7.0-json php7.0-mysql php7.0-curl php7.0-mbstring
apt install php7.0-intl php7.0-mcrypt php-imagick php7.0-xml php7.0-zip
systemctl restart apache2

cd /var/www
mkdir nextcloud
wget https://download.nextcloud.com/server/releases/nextcloud-15.0.0.zip
sha256sum -c <(wget -q https://download.nextcloud.com/server/releases/nextcloud-15.0.0.zip.sha256 -O -) < nextcloud-15.0.0.zip
unzip nextcloud-15.0.0.zip
chown -R www-data:www-data /var/www/nextcloud/

mysql -u root -p # No password is required, just hit enter
CREATE USER 'nextclouduser'@'localhost' IDENTIFIED BY 'Password';
create database nextcloud;
GRANT ALL ON nextcloud.* TO 'nextclouduser'@'localhost';
flush privileges;

mysql -u nextclouduser -p # Make sure it worked

a2enmod ssl
a2ensite default-ssl # Enable HTTPS website

apachectl -M # Check modules enabled
# Enable the following if not already done
a2enmod rewrite
a2enmod headers
a2enmod env
a2enmod dir
a2enmod mime
systemctl restart apache2
systemctl disable apache2 # No auto start on boot

Now edit /etc/apache2/sites-enabled/000-default.conf. It must contain the following (note that the redirection to https can be automatically added by Let's Encrypt, see farther below):

Alias /nextcloud "/var/www/nextcloud/"
<Directory "/var/www/nextcloud">
Options +FollowSymLinks
AllowOverride All

<IfModule mod_dav.c>
        Dav off

SetEnv HOME /var/www/nextcloud
SetEnv HTTP_HOME /var/www/nextcloud

<Directory "/mnt/data_partition/">
# just in case if .htaccess gets disabled
    Require all denied

<VirtualHost *:80>
        ServerName cloud.romainpellerin.eu
        ServerAdmin romain@romainpellerin.eu
        DocumentRoot /var/www/nextcloud

        RewriteEngine on
        RewriteCond %{HTTPS} off
        RewriteRule ^(.*)$ https://%{HTTP_HOST}$1 [END,QSA,R=permanent]

        ErrorLog ${APACHE_LOG_DIR}/error.log
        CustomLog ${APACHE_LOG_DIR}/access.log combined

Bring the changes between <VirtualHost> tags to /etc/apache2/sites-enables/default-ssl.conf, except for the instruction Redirect. Additionally, add instructions taken from Mozilla SSL Configuration Generator:

<VirtualHost *:443>
    SSLEngine on

    # HSTS (mod_headers is required) (15768000 seconds = 6 months)
    Header always set Strict-Transport-Security "max-age=15768000"

# modern configuration, tweak to your needs
SSLProtocol             all -SSLv3 -TLSv1 -TLSv1.1
SSLHonorCipherOrder     on
SSLCompression          off
SSLSessionTickets       off

# OCSP Stapling, only in httpd 2.3.3 and later
SSLUseStapling          on
SSLStaplingResponderTimeout 5
SSLStaplingReturnResponderErrors off
SSLStaplingCache        shmcb:/var/run/ocsp(128000)

To enable HTTP2, do a2enmod http2 and edit default-ssl.conf again:

<VirtualHost *:443>
    ProtocolsHonorOrder On
    Protocols h2 h2c http/1.1
    H2Direct on

Note that Apache prefork is not compatible with HTTP2. You'll have to use fpm. See Nextcloud's instructions also.

Let us now improve a bit Apache's security. Edit /etc/apache2/conf-enabled/security.conf like this:

ServerSignature Off
Header set X-Frame-Options: "sameorigin" # Require mod_headers
ServerTokens Prod

Now, visit http://raspberry-pi-IP/nextcloud once. This will create /var/www/nextcloud/config/config.php. Edit this file like this:

'overwrite.cli.url' => 'https://example.org/',
'htaccess.RewriteBase' => '/',

Now edit /etc/apache2/sites-enable/000-default.conf like this:

Alias / "/var/www/nextcloud/"

And restart systemctl restart apache2. You should now be able to visit http://raspberry-pi-IP/.

Now get a browser-trusted certificate from Let's Encrypt:

apt install dirmngr
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 7638D0442B90D010
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 8B48AD6246925553
echo "deb http://ftp.debian.org/debian stretch-backports main" > /etc/apt/sources.list.d/backports.list
apt update
apt install python-certbot-apache -t stretch-backports
# Make sure your website is accessible from the Internet.
# Set up NAT/PAT rules in your router. Then:
certbot --apache
certbot renew --dry-run # Try renewal
# If successful, create a cronjob to be run twice a day
32 4/12 * * * certbot renew --quiet

Ultimately, verify everything is all right using SSL LABS's SSL server test.

You may now restart Apache2.


mkdir /mnt/data_partition/nextcloud_data
chown -R www-data:www-data /mnt/data_partition/nextcloud_data/
chmod 750 /mnt/data_partition/nextcloud_data

Now you can start setting up Nextcloud at https://raspberry-pi-IP/.

Improve PHP's performance

sed "s/;opcache.enable=0/opcache.enable=1/" \
    -i /etc/php/7.0/apache2/php.ini
sed "s/;opcache.enable_cli=0/opcache.enable_cli=1/" \
    -i /etc/php/7.0/apache2/php.ini
sed "s/;opcache.revalidate_freq=2/opcache.revalidate_freq=240/" \
    -i /etc/php/7.0/apache2/php.ini
sed "s/memory_limit = 128M/memory_limit = 512M/" \
    -i /etc/php/7.0/apache2/php.ini
sed "s/upload_max_filesize = 2M/upload_max_filesize = 16G/" \
    -i /etc/php/7.0/apache2/php.ini
sed "s/post_max_size = 8M/post_max_size = 16G/" \
    -i /etc/php/7.0/apache2/php.ini
sed "s/output_buffering = 4096/output_buffering = 0/" \
    -i /etc/php/7.0/apache2/php.ini
sed "s/max_input_time = 60/max_input_time = 3600/" \
    -i /etc/php/7.0/apache2/php.ini
sed "s/max_execution_time = 30/max_execution_time = 3600/" \
    -i /etc/php/7.0/apache2/php.ini

# /etc/php/7.0/cli/php.ini is used by Nextcloud's CRON jobs
sed "s/;opcache.enable=0/opcache.enable=1/" \
    -i /etc/php/7.0/cli/php.ini
sed "s/;opcache.enable_cli=0/opcache.enable_cli=1/" \
    -i /etc/php/7.0/cli/php.ini
sed "s/;opcache.revalidate_freq=2/opcache.revalidate_freq=240/" \
    -i /etc/php/7.0/cli/php.ini
sed "s/output_buffering = 4096/output_buffering = 0/" \
    -i /etc/php/7.0/cli/php.ini
sed "s/max_input_time = 60/max_input_time = 3600/" \
    -i /etc/php/7.0/cli/php.ini
sed "s/max_execution_time = 30/max_execution_time = 3600/" \
    -i /etc/php/7.0/cli/php.ini

To significantly improve performance overall, you'll also need data caching: APCu.

apt install php7.0-apcu
phpenmod apcu
#echo "apc.enabled=1" >> /etc/php/7.0/cli/conf.d/20-apcu.ini # Normally it's useless, check Nextcloud error logs in the admin page to make sure it's working
service apache2 restart

Now, add the following in /var/www/nextcloud/config/config.php:

'memcache.local' => '\OC\Memcache\APCu',

Restart Apache. Running php -i will say opcache.enable => On and Opcode Caching => Disabled. That's normal as we didn't enable opcaching for CLI, (/etc/php/7.0/cli/php.ini), only for Apache2. However, if you create a webpage containing <?php phpinfo();, when accessing this page you'll see that's it's Up and Running. Make sure as well that APCu is enabled.

Improve Nextcloud's settings

Add the following in /var/www/nextcloud/config/config/php:

'logtimezone' => 'Europe/Paris',

In Nextcloud, enable the server-side encryption in the admin settings, and enable the app called Encryption in the web interface, while logged in as an admin. You'll need to log out and in to actually enable encryption for good.

Also, do:

cd /var/www/nextcould
sudo -u www-data php occ maintenance:update:htaccess

It will allow Nextcloud to change these settings directly from the Admin webpage. It will also update some settings.

In the admin page (URL/settings/admin), increase the maximum upload size to 1.9 GB (it's a limitatiom from 32-bit PHP - Rasbian is a 32 bit OS).

In URL/settings/admin/overview you might get some warnings about PHP not being properly configured. Edit /etc/php/7.0/apache2/php.ini as it asks.

Install and enable the app "Two Factor TOTP Provider" in URL/settings/apps. Then, go to URL/settings/user/security and enable TOTP.

OpenVPN 2.4.0

apt update && apt install openvpn

Read the official documentation (here, short tutorial for easy-rsa3). Download easy-rsa and checkout latest release.

cd /etc/openvpn/
git clone https://github.com/OpenVPN/easy-rsa.git
cd easy-rsa
git checkout v3.0.5
cd easyrsa3
cp vars.example vars
echo "set_var EASYRSA_KEY_SIZE       2048" >> vars # No need to source this file

From now on, I highly recommend you read /etc/openvpn/easy-rsa/doc/EasyRSA-Readme.md and https://github.com/OpenVPN/easy-rsa/blob/master/README.quickstart.md in order to continue setting up OpenVPN. As explained, you need to create a PKI to get three distinct things: your CA, a certificate and private key for the server and another couple of this kind for clients. Normally you should generate the pair for clients on your personnal computer, however it's not necessary in our case (who cares about security anyway?).


./easyrsa init-pki
./easyrsa build-ca # Add a strong password which will be used to sign other certificates
./easyrsa gen-req server nopass # Do not add a password for the server; use 'server' as CN
./easyrsa sign-req server server
./easyrsa gen-req client # Use 'client' as CN; add a passphrase that you will need later when you try to connect to your VPN server
./easyrsa sign-req client client

Once the certificates and private keys are generated for the server and a client at least, do the following on your Pi and then go back to reading OpenVPN's documentation:

./easyrsa gen-dh
cp /usr/share/doc/openvpn/examples/sample-config-files/server.conf.gz /etc/openvpn/
cp /usr/share/doc/openvpn/examples/sample-config-files/client.conf /etc/openvpn/
cd /etc/openvpn
openvpn --genkey --secret ta.key
gunzip server.conf.gz
vim server.conf

You should edit server.conf like this:

port 1194
proto udp
dev tun
ca /etc/openvpn/easy-rsa/easyrsa3/pki/ca.crt
cert /etc/openvpn/easy-rsa/easyrsa3/pki/issued/server.crt
key /etc/openvpn/easy-rsa/easyrsa3/pki/private/server.key
dh /etc/openvpn/easy-rsa/easyrsa3/pki/dh.pem
push "route"
push "redirect-gateway def1 bypass-dhcp"
push "dhcp-option DNS"
push "dhcp-option DNS"
tls-auth /etc/openvpn/ta.key 0
cipher AES-256-CBC
max-clients 2
user nobody
group nogroup
log-append  openvpn.log
verb 6
mute 20
script-security 3
client-connect "/etc/openvpn/notifyconnect.sh"

Keep the rest as-is. Now create /etc/openvpn/notifyconnect.sh:

echo "On `date`" | mail -s "OpenVPN client connection" root@localhost 2>/dev/null
sleep 1
# We can't run `runq`  or `exim -qff` since this script is called by user `nobody`.
# Also make sure this script returns 0 otherwise the VPN connection will fail.


chmod +x /etc/openvpn/notifyconnect.sh

Then add CAP_SYS_RESOURCE to CapabilityBoundingSet in /lib/systemd/system/openvpn@.service. Check OpenVPN logs, there might be a permission issue on /var/log/exim4.

Now, edit client.conf:

remote <your DynHost domain> 1194
user nobody
group nogroup
# Normally next lines are uncommented but we won't need them
#ca /etc/openvpn/easy-rsa/easyrsa3/pki/ca.crt
#cert /etc/openvpn/easy-rsa/easyrsa3/pki/issued/client.crt
#key /etc/openvpn/easy-rsa/easyrsa3/pki/private/client.key
#tls-auth /etc/openvpn/ta.key 1
#ns-cert-type server # OpenVPN 2.0 and below
remote-cert-tls server # OpenVPN 2.1 and above
cipher AES-256-CBC
mute 20

Ultimately do:

cat /etc/openvpn/client.conf > client.ovpn
echo "key-direction 1" >> client.ovpn
echo "script-security 2" >> client.ovpn
echo "up /etc/openvpn/update-resolv-conf" >> client.ovpn
echo "down /etc/openvpn/update-resolv-conf" >> client.ovpn
echo "<ca>" >> client.ovpn
cat /etc/openvpn/easy-rsa/easyrsa3/pki/ca.crt >> client.ovpn
echo "</ca>" >> client.ovpn
echo "<cert>" >> client.ovpn
cat /etc/openvpn/easy-rsa/easyrsa3/pki/issued/client.crt | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' >> client.ovpn
echo "</cert>" >> client.ovpn
echo "<key>" >> client.ovpn
cat /etc/openvpn/easy-rsa/easyrsa3/pki/private/client.key >> client.ovpn
echo "</key>" >> client.ovpn
echo "<tls-auth>" >> client.ovpn
cat /etc/openvpn/ta.key >> client.ovpn
echo "</tls-auth>" >> client.ovpn

Copy that client.ovpn file on your client.

On your Pi, uncomment the following line in /etc/sysctl.conf:


Apply changes:

sysctl -p

There's a known bug which may prevent these values to be read on boot (check that it worked about rebooting with sysctl -a | grep ip_forward). Add the following above exit 0 in /etc/rc.local:

sysctl -p /etc/sysctl.conf

Don't forget to set up a port forward rule to forward UDP port 1194 from your gateway/router to the machine running the OpenVPN server. In addition, allow incomings UDP connections on port 1194 and these rules:

iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
iptables -A FORWARD -s -j ACCEPT
iptables -A FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT

Finally, do the following on your server:

cd /etc/openvpn
mv client.conf client.conf.old
shred -u client.ovpn
openvpn --config server.conf &
tail -f openvpn.log

At next boot, the server will run automatically. We needed to rename the client.ovpn because the initscript will scan this directory for .conf files and start up a separate OpenVPN deamon for each file found. It is recommended to delete client's files:

shred -u client.conf.old
shred -u /etc/openvpn/easy-rsa/easyrsa3/pki/private/client.key
shred -u /etc/openvpn/easy-rsa/easyrsa3/pki/issued/client.crt

Now your client:

sudo apt update && sudo apt install resolvconf
sudo openvpn --config client.ovpn

You might want to make your VPN server available on several ports. If so, open the ports you wish to use on your router and add the corresponding iptables rules, for instance:

iptables -t nat -A PREROUTING -i eth0 -p udp --dport 53 -j REDIRECT --to-port 1194

Hardening security

wget --no-check-certificate https://raw.githubusercontent.com/rpellerin/dotfiles/master/scripts/firewall.sh
chmod 700 firewall.sh
chown root:root firewall.sh
mv firewall.sh /etc/init.d
update-rc.d firewall.sh defaults

In the firewall, you might want to comment the line NETWORK_MGMT= in order to be able to log in using SSH from any LAN, including your VPN.

Another helpful website about how to prevent SSH bruteforce attacks. However, we'll use fail2ban, see below.

Consider allowing SSH connections using public keys only. On a client do:

ssh-keygen -t ed25519 -o -a 100 # Accept default directory and no passphrase
ssh-keygen -t rsa -b 4096 -o -a 100 
ssh-copy-id -i ~/.ssh/id_rsa.pub pi@raspberrypi-IP

Hardening SSH configuration

Edit /etc/ssh/sshd_config:

Port <something you like>
#HostKey /etc/ssh/ssh_host_dsa_key # Comment because too old
#HostKey /etc/ssh/ssh_host_ecdsa_key # Same reason
UsePrivilegeSeparation sandbox
LoginGraceTime 10s
PermitRootLogin no
StrictModes yes
PubkeyAuthentication yes
IgnoreRhosts yes
PermitEmptyPasswords no
ChallengeResponseAuthentication no
PasswordAuthentication no # Or yes, depending on your needs
Banner /etc/issue.net # Message displayed at login
UsePAM no

# Custom settings
AllowUsers pi # ONLY pi will be allowed to log in

Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr
MACs hmac-sha2-512,hmac-sha2-256,hmac-ripemd160
KexAlgorithms curve25519-sha256@libssh.org,diffie-hellman-group-exchange-sha256

The last three lines are taken from this very helpful website. You should also read this one after having read the first one (the order is important).

Sending an email on every SSH connection

Taken from this thread. As root, create /etc/ssh/login-notify.sh:


if [ "$PAM_TYPE" != "close_session" ]; then 
    subject="SSH Login: $PAM_USER from $PAM_RHOST on $host" 
    echo "$message" | mail -s "$subject" root@mydomain

Note though that you will need to change this line in /etc/ssh/sshd_config:

UsePAM yes


chmod +x /etc/ssh/login-notify.sh

And add the following line at the end of the file /etc/pam.d/sshd:

session optional pam_exec.so seteuid /etc/ssh/login-notify.sh

Now restart sshd by doing service sshd restart.


Official documentation.

apt install fail2ban
fail2ban-client -x start # To force the server to startup. However, a reboot is preferable.

Make sure the file /etc/init.d/fail2ban exists. Now edit /etc/fail2ban/jail.local (make a copy of /etc/fail2ban/jail.conf):

# Whatever fits you
bantime = 86400
findtime = 3600
maxretry = 3
ntime  = 8700
action = %(action_mwl)s

# Enable sshd (enabled = true) and sshd-dos, change 'port' and the 'maxretry' value if need be
# Enable apache, apache-*
# Add the following new entry
enabled = true
port = http,https
filter = http-dos
logpath = /var/log/apache*/*access.log
maxretry = 360
findtime = 120
bantime = 600

enabled = true
port = http,https
filter = http-dos
logpath = /var/log/apache*/*access.log
maxretry = 1
findtime = 120
bantime = 6000
banaction = ban-countries
action = %(action_)s
# action_ won't send email when banning someone (cause would send a message for every new request) nor when starting/stopping

Now create /etc/fail2ban/filter.d/http-dos.conf:


# Option: failregex
# Note: This regex will match any GET or POST entry in your logs, so basically
# all valid and not valid entries are a match.
# You should set up in the jail.conf file, the maxretry and findtime carefully
# in order to avoid false positives.

failregex = ^<HOST> -.*\"(GET|POST).*

# Option: ignoreregex
# Notes.: regex to ignore. If this regex matches, the line is ignored.
# Values: TEXT

ignoreregex =

Now create /etc/fail2ban/action.d/ban-countries.conf:

# Copied from iptables-allports.conf

actionstart = <iptables> -N f2b-<name>
              <iptables> -A f2b-<name> -j <returntype>
              <iptables> -I <chain> -p <protocol> -j f2b-<name>

actionstop = <iptables> -D <chain> -p <protocol> -j f2b-<name>
             <iptables> -F f2b-<name>
             <iptables> -X f2b-<name>

actioncheck = <iptables> -n -L <chain> | grep -q 'f2b-<name>[ \t]'

actionban = IP=<ip> &&
            COUNTRY=$(geoiplookup $IP | egrep "<country_list>") && [ "$COUNTRY" ] &&
            <iptables> -I f2b-<name> 1 -s <ip> -j <blocktype> || true

actionunban = true


country_list = CN|China

Now install missing packages, reload the service maually to make sure there is no error:

apt install geoip-bin geoip-database
systemctl stop fail2ban
fail2ban-client -x start
cat /var/log/fail2ban.log
# Check for errors
fail2ban-client -x stop
systemctl start fail2ban


Should you have a beeping hard disk drive, the reason might be power consumption. It usually beeps when it needs more electricity. Disabling Advanced Power Management will solve this problem in most cases (sudo hdparm -B 255 /dev/sda).

Going further

Pro tips

Consider buying an uninterruptible power supply (UPS) for your Raspberry to prevent damage caused by power outage.


A year ago, I gave a talk at HumanTalks Compiègne about my Raspberry Pi. Here are the video and the slides.

Slides are available in HTML.

Interesting external links

Read-only Raspberry Pi