How to host a website on your raspberry pi

On this page, I explain how I set up my raspberry pi as a server to host my personal website. In future, I might also add guides for other projects as well. For example, I am also using my raspberry pi as a personal cloud, as a replacement of commercial services like dropbox or google drive. And, I am really excited about this: I have been running mailpile on my raspberry pi for almost a year now! Iceland rules!
I want this guide to help other people with little or no technical background reclaim control over their own data and make their contribution towards a decentralisation of the internet.

Headless Installation of Raspbian Stretch

A raspberry pi is a computer without a monitor, keyboard nor mouse—it is "headless" in the lingo of computer experts. One can connect a raspberry pi to such user interfaces, but (like with most other computers) this is not strictly necessary for it to run and do its job properly. Instead, we will communicate with the raspberry pi from another computer over a terminal, see step 2.

Things you'll need:

  • a raspberry pi, such as 3 model b or 3 model b+, with an ethernet cable (to connect to your router) and power supply
  • a micro SD card (>4GB, say)
  • a computer with a micro SD card reader
  • a router
To set up a website, you also need internet access, of course. Some internet service providers (ISPs) block certain ports that we will want use, such as port 80. This complicates certain things like obtaining certificates for https. For now, I will be assuming that you are lucky and your ISP does not do such silly things.

Overview

  1. Preparation of your SD card
  2. Start talking!
  3. Basic configuration
  4. Passwords
  5. ssh setup
  6. Getting ready for the internet
  7. Port forwarding
  8. Setting up an apache server
  9. Domain names and dynamic DNS
  10. Let's Encrypt!

Step 1: Preparation of your SD card. The SD card is the heart of your raspberry pi, as it will contain the operating system. To get the operating system onto the SD card, we use our normal computer. First, we download the latest version of Raspbian (an operating system designed especially for the raspberry pi) from here to our computer. Then, we plug the SD card into the computer and install the downloaded file on the SD card. There are plenty of references online for how this installation is done, e.g. this one, so I will not explain this step. If your computer runs on ubuntu, one can for example use the Startup Disk Creator.
Once we have successfully installed the operating system on our SD card, there should be two partitions, one of which is called boot. To access the raspberry pi from our computer, we need to put an empty text file called ssh into the boot partition. Note: There might also be a folder called boot in the other partition, so make sure not to confuse one with the other.
Finally, we remove the SD card from the computer and plug it into the raspberry pi. We then connect the raspberry pi to our router using an ethernet cable. We are now ready to switch on the raspberry pi, simply by connecting it to the grid.

Step 2: Start talking! Next, you need to find the local IP address that the router assigned to the raspberry pi when establishing the ethernet connection. For this, you log into the router by entering its local IP address into the address bar of a browser. The address usually looks something like 192.168.0.1 or 192.168.1.1. This information should be provided by your ISP or be found in the manual of the router. Once you have logged into your router, you navigate to the list of devices connected to it. One of them should be called raspberrypi and should have an IP address assigned to it, which looks something like 192.168.0.104. To make sure that your computer can talk to the raspberry pi, we ping like so

computer$ ping -c 5 the-ip-of-your-raspberry-pi

You should see a response as follows:

computer$ ping -c 5 the-ip-of-your-raspberry-pi
PING 192.168.0.152 (192.168.0.152) 56(84) bytes of data.
64 bytes from the-ip-of-your-raspberry-pi: icmp_seq=1 ttl=64 time=0.121 ms
64 bytes from the-ip-of-your-raspberry-pi: icmp_seq=2 ttl=64 time=0.076 ms
64 bytes from the-ip-of-your-raspberry-pi: icmp_seq=3 ttl=64 time=0.069 ms
64 bytes from the-ip-of-your-raspberry-pi: icmp_seq=4 ttl=64 time=0.075 ms
64 bytes from the-ip-of-your-raspberry-pi: icmp_seq=5 ttl=64 time=0.069 ms

--- the-ip-of-your-raspberry-pi ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 4100ms
rtt min/avg/max/mdev = 0.069/0.082/0.121/0.019 ms

If instead, you see a message like destination host unreachable, you should check if you are using the correct IP address.
If you have found the right IP address, you should make sure that your router always assigns the same address to your raspberry pi, even if the router or the raspberry pi or both are switched off for a while. For this, go to the DHCP settings on your router and assign a static local IP address to your raspberry pi. (Router software varies a lot, so there are no universal instructions for how this is done; please consult the manual of your router.)
Let us continue and finally try to connect to the raspberry pi as follows:

computer$ ssh pi@the-ip-of-your-raspberry-pi

SSH is a standard network protocol that provides secure communication between two devices over the internet, or in this particular case, between your computer and your raspberry pi in the local network of our router. If ssh works, we should be asked for the default password of the default user pi on the raspberry pi. This password is raspberry.
If something fails, you can call ssh in verbose mode using the option -vvv, which should give you more information on what has gone wrong. Otherwise, we are now logged into the raspberry pi and can start the configuration.

Step 3: Basic configuration. First, let us update the software:

raspberrypi$ sudo apt-get update
raspberrypi$ sudo apt-get upgrade

At the moment, there is only one user account on our raspberry pi, namely pi, who has root privileges. It is good practice to not log into linux (or in fact any system) as root for day-to-day use. For an explanation why, see for example here. So we want to set up a different user, who has fewer privileges. In the following, we call this user standarduser; but you might want to be more creative:

raspberrypi$ sudo useradd standarduser
raspberrypi$ sudo mkdir /home/standarduser
raspberrypi$ sudo mkdir /home/standarduser/.ssh
raspberrypi$ sudo chmod 700 /home/standarduser/.ssh

Step 4: Passwords. Next, we set a new passphrase for the user pi. We type

raspberrypi$ passwd

and follow the instructions in the terminal. Since we ultimately want to make the raspberry pi visible to the whole world, we should use a long and secure passphrase; see for example here on how to choose a good one. Also set a passphrase for the new user:

raspberrypi$ sudo passwd standarduser

If you decide that standarduser needs admin privileges, call

raspberrypi$ sudo visudo

This opens the file /etc/sudoers.tmp with the standard text editor nano. Change the entry for standarduser such that it looks as follows:

root ALL=(ALL:ALL) ALL
standarduser ALL=(ALL:ALL) ALL
You exit the text editor by pressing Ctrl+X. Finally, to make the terminal more user friendly, enable bash like so:
raspberrypi$ sudo chsh -s /bin/bash standarduser
You can now use the up and down keys to scroll through previously executed commands.

Step 4: ssh setup. If you log out of your current ssh session with you raspberry pi (the command for that is exit), and log back in using your new user account, like so

computer$ ssh standarduser@the-ip-of-your-raspberry-pi

you'll be prompted to enter the passphrase chosen previously for standarduser. However, we ultimately want to enable your computer and your raspberry pi to communicate on their own (eg for automated backups, synchronisation, etc), and it would be annoying if you had to enter your passphrase each time. So let us enable automatic authentication. For this, we need to create a key on our computer and configure ssh such that it only allows access with this particular key. We start with the keypair generation (on our computer, not on the raspberry pi!):

computer$ ssh-keygen -t rsa -b 4096

Make sure, you do not add sudo to the above, otherwise you create a new root ssh-key. When prompted, leave the passphrase empty. Now navigate into the .ssh directory of the home directory on your computer and find the file id_rsa.pub. Add its contents (and any other public keys that you want to allow access to this server) to the following file on the raspberry pi:

raspberrypi$ nano /home/standarduser/.ssh/authorized_keys

Then run the following commands to set the correct permissions for the new file and your home directory of standarduser:

raspberrypi$ sudo chmod 400 /home/standarduser/.ssh/authorized_keys
raspberrypi$ sudo chown standarduser:standarduser /home/standarduser -R

Finally, we want to make sure that noone can access your raspberry pi without those keys. For this, open the file /etc/ssh/sshd_config with sudo rights

raspberrypi$ sudo nano /etc/ssh/sshd_config
and append the following lines
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes

Then restart ssh:

raspberrypi$ sudo service ssh restart

If you log out and back in again, you'll see that you did not need to type in your passphrase anymore. Neat!

Step 6: Getting ready for the internet. At the moment, your raspberry pi sits safely behind your router, which (hopefully) blocks all attempts to penetrate your network. However, we will soon change this, so let's set up some safeguards on our raspberry pi.
First, let us set up fail2ban. This is a daemon (a programme that runs in the background) that monitors login attempts to a server and blocks suspicious activity as it occurs, such as unauthorized ssh requests (which are fairly common). To install this programme, type

raspberry pi$ sudo apt-get install fail2ban

Then, navigate to /etc/fail2ban and copy jail.conf to jail.local.

raspberry pi$ cd /etc/fail2ban
raspberry pi$ cp jail.conf jail.local

Open jail.local and make the entry under [sshd] look something like this:

[sshd]

port = ssh
logpath = %(sshd_log)s
backend = %(sshd_backend)s
enabled = true
banaction = iptables-multiport
maxretry = 4
bantime = 1000000

For more detailed advice on how to configure fail2ban, see for example here.
Next, let us set up a firewall.

raspberrypi$ sudo apt-get install ufw

Before I could use ufw, I first had to restart the raspberry pi. You can do this as follows:

raspberrypi$ sudo shutdown -r now
After logging back in, we open ports on the raspberry pi:
raspberrypi$ sudo ufw allow 22
raspberrypi$ sudo ufw allow 80
raspberrypi$ sudo ufw allow 443
raspberrypi$ sudo ufw enable
Port 22 is for ssh, port 80 is for http websites, 443 is for https websites. Now check the status of ufw:
raspberrypi$ sudo ufw status

The output be something like this:

Status: active

To         Action  From
--         ------  ----
22         ALLOW   Anywhere
80         ALLOW   Anywhere
443        ALLOW   Anywhere
22 (v6)    ALLOW   Anywhere (v6)
80 (v6)    ALLOW   Anywhere (v6)
443 (v6)   ALLOW   Anywhere (v6)

As a final step before we open our front gates, let us enable automatic security updates:

raspberrypi$ sudo apt-get install unattended-upgrades

Then open

raspberrypi$ sudo nano /etc/apt/apt.conf.d/10periodic
and make it look as follows
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Download-Upgradeable-Packages "1";
APT::Periodic::AutocleanInterval "7";
APT::Periodic::Unattended-Upgrade "1";

There is one more config file to edit. Make sure that /etc/apt/apt.conf.d/50unattended-upgrades contains the following lines:

Unattended-Upgrade::Origins-Pattern {
...
"origin=Debian,codename=${distro_codename},label=Debian-Security";
...
};

Step 7: Port forwarding. At the moment, you can only access your raspberry pi from within your network. For this, you use the local IP address of your raspberry pi, which was assigned by your router. The router has a local IP address, too; in fact, we already used it when logging in to the router earlier. However, the router also has a public IP address, which was assigned by your ISP. This address is used for all communication between other computers and networks (ie the internet) and your network -- so in particular, when they want to talk to your raspberry pi. But at the moment, your router does not know when requests from outside should go to your raspberry pi or to your computer or to any other device in your network. So we now need to tell the router that it should forward any ssh, http and https requests to the raspberry pi. Each of these requests use a particular port, namely 22, 80 and 443, respectively.
In summary, we need to tell the router that it should forward any incoming traffic on the ports 22, 80 and 443 to the IP address of the raspberry pi (and the respective ports). We should also make sure that the firewall of the router does not block any of these ports. Again, I need to refer you to the manual of your router, since routers and router software vary a lot. Make sure to reboot the router every time you change settings; otherwise, they might not take effect.
If everything works, you should get successful results using port checking websites like this one. Note that some ISPs block port 80, so you might be unable to open it. In that case, you can still use ssh and https, but obtaining a https certificate from Let'sEncrypt will very likely be more complicated.

Step 8: Setting up an apache server. Installing an apache webserver is easy; it comes preconfigured out of the box:

raspberrypi$ sudo apt-get install apache2

To check reliably if the server is indeed up and running, you should access it from outside your network. The easiest way to do this is to open the Tor browser and typing in your current public IP address (which you can find out here). Accessing this address from within your network might not work, since the traffic request first leaves through your router and then comes straight back again, which confuses some routers (such as mine). Instead, to test if apache is at least visible in your local network, you can use the local IP address of your raspberry pi. In any case, if apache is running properly, you should see the standard apache2 Debian default test page.
If you already have some website files on your computer that you want to use, copy them to the user directory on your raspberry pi. We have not done that yet, so here goes: Navigate to the directory containing the folder public_html, say, that you want to transfer including all its contents. Then type

computer$ scp -r public_html standarduser@ the-ip-of-your-raspberry-pi :public_html

Now go to the raspberry pi and give read permissions to this new directory of the website:

raspberrypi$ chmod 711 ~ ~/public_html
raspberrypi$ chmod -R a+rX ~/public_html/*

Now change the root directory for the apache server. For this, edit the line

/var/www/html

in /etc/apache2/sites-available/000-default.conf to

/home/standarduser/public_html

Similarly, go to the file /etc/apache2/apache2.conf and change /var/www/html/ (or /var/www/) in the first line of

<Directory /var/www/html/>
Options FollowSymLinks
AllowOverride None
Require all granted
</Directory>

to

/home/standarduser/public_html/

Now restart the server again:

raspberrypi$ sudo service apache2 restart

You should now be able to see your own website.

Step 9: Domain names and dynamic DNS. We have now successfully set up a website on the internet. However, noone knows how to find it yet. There are, in fact, two problems: First, if you have a standard residential account with your ISP, you probably only have a dynamic IP address, meaning it can change anytime without prior notice. Second, an IP address is not particularly memorable.
Both of these issues can be fixed by getting a domain name, a human readable address in addition to your IP address. This address is stored on a domain name server (DNS) which, like in a phone book, sends any traffic addressed to your domain name to your IP address instead. We can then make sure that this link between domain name and dynamic IP address on the domain name server gets updated, whenever your IP address changes.
Usually, one needs to pay for a domain name, both the domain name registrar, who manages the reservation of Internet domain names, and the company which hosts the domain name servers. If you don't want to spend money and aren't that picky about what your domain name should be, you can use freeDNS. Just get an account, choose a subdomain (eg yourname.jumpingcrab.com) and connect it to your current IP address. If you want your website to be visible to google, follow these instructions.
So it remains to set up Dynamic DNS. Again, freeDNS is your friend. Navigate to "Dynamic DNS" in the top menu on the left. Here, you can chose a number of different ways to automatically update your IP address. The easiest option is to set up a cron job, ie a process which is regularly executed on your raspberry pi. For detailed instructions on how this is done, click "quick cron example".
To test, if your website works, just enter your domain name in the address bar of your browser. Note, however, that you might have the same problem as with the public IP address earlier. So to be sure, use the Tor browser instead to test your website.

Step 10: Let's Encrypt! It remains to obtain a certificate from a certificate authority (CA). This certificate is used to establish a https (s=secure) connection to your website. This is useful, for example if you have forms on your website where you ask people for personal information. Or if you want to host your own mailclient like mailpile on your raspberry pi, and don't want everyone to read your communication. Or if you want to make sure that no other website can pretend to be yours. Or if you just want to follow good practice.
Thanks to Let's Encrypt, obtaining a certificate is super-easy nowadays. Well, as long as ports 80 and 443 are open. If port 80 is blocked, you need to fiddle around a bit. Let's assume you are lucky and both ports are open.
To obtain a Let's Encrypt certificate, we need to install certbot, a program that does most of the work for us. Currently, there does not exist a certbot package for Raspbian. But, following these instructions, that is no problem, we can get a working version of certbot without apt-get just like so:

raspberrypi$ wget https://dl.eff.org/certbot-auto

There should now be a file certbot-auto in your home directory. (You can check this with ls -al, which shows you all files in your current directory.) Next, make this file executable:

raspberrypi$ chmod a+x certbot-auto

Type

raspberrypi$ sudo ./certbot-auto --apache

and follow the instructions. If everything works, you are done!
Well, not quite: certificates from Let's Encrypt expire after 90 days. Certbot can be configured to renew your certificates automatically. You can test automatic renewal for your certificates by running this command:

raspberrypi$ sudo ./certbot-auto renew --dry-run

If it works, you should add certbot as a cron job. (Remember from step 9 that cron jobs are configured by typing crontab -e.) Just add the following line to cron:

0 0,12 * * * python -c 'import random; import time; time.sleep(random.random() * 3600)' && ./path/to/certbot-auto renew
Done! Now certbot will check twice a day (at a random time) if the certificate is about to expire and, if necessary, get it renewed.
last updated on 25 August 2018
Back to my website.