When Heroku got rid of their free tier, it wasn’t even like I had a bunch of web apps on there that I really needed to migrate … I think there was just one (my media server “Video Streamer”). But, I decided maybe this was the right time to switch to self hosting. I did have a Raspberry Pi sitting around, after all.

Since then, I’ve gotten pretty into it, and am running a number of different services on my Pi. Once you get the hang of it, it becomes really fast to deploy things; faster than setting a traditional hosting service, and also there’s a lot of flexibility to add random little microservices and what not.

So, I’m not going to give comprehensive instructions here, but rather just mention the most important things you need to get done. There will also be some concepts explained along the way.

  1. Set up SSH.
    • At the very beginning, you will need to plug a monitor and keyboard into your PI, but after you set up SSH you will never need to do that again. You can follow the instructions here https://raspberrypi-guide.github.io/networking/connecting-via-ssh

    • An important concept to understand here is private vs public IP address. When you run hostname -I that is giving you the private IP. You can connect to your PI from another computer on the same network using ssh username@192.168.x.xx but this will not work to connect from any different WiFi network.

    • If you want to enable SSH from any network, you need to go into your router settings - for my RockSpace mesh router, it’s a mobile app, but some routers have a special URL you can access from your computer. From here, you need to go to the port forwarding section, and basically expose your Raspberry Pi’s port 22 to the public internet. When you go to the router’s port forwarding page, you should be able to map the public port 22 to a combination of two things - your Raspberry Pi device, and it’s private 22.

    • Once you set up port forwarding, you can connect from anywhere using your public IP address. You can google “whats my public IP” to find this out. Hopefully yours is stable (my Sonic ISP seems to give me a stable one) but I have heard that some ISPs require you to pay more for a stable public IP (one that does not change) - and having a stable one is essential for self hosting because when you buy a domain name, you’re going to be mapping that to your public IP so it can’t just be changing all the time.

    • If you decide you do want to go ahead with the port forwarding, it’s a good idea to change your SSH config to only allow key-based authentication. This means that you won’t have to be passing your username / password over the wire every time you connect. A google search will give you instructions for how to do this. You will need to generate public/private key pairs on each authorized computer and append the public key to ~/.ssh/authorized_keys on the PI. This is essentially the same thing you do when you set up key-based authentication on Github.

    • One bonus of enabling SSH is that you have incidentally also enabled FTP (well, more accurately, SFTP, which is FTP that communicates over the SSH port 22). So, you can use any common FTP program such as Filezilla to transfer files back and forth.

  2. Running graphical programs

    • It is possible to run graphical programs on your PI, and have the windows show up on the computer you’re connecting from. For this you basically need to install an X display server on the computer you’re connecting from. On OSX I’m using one called XQuartz. Then use ssh -Y instead of ssh and hopefully it should just work? There is a built in text editor called “geany”, and you can use “open /path/to/dir” to show the file manager. And so on.
  3. Running websites, the quick and dirty way

    • Step 1 is to run your application server as normal. Note which port it’s running on.

    • Right off the bat, you should be able to connect using your private IP, if you’re on the same network. Let’s say it’s port 3000 - Just go to 192.168.x.xx:3000 in your browser and voila.

    • To expose it to the public internet, you just need to expose 3000 via port forwarding, the same as you did for 22 when setting up SSH. Note that you will need to enter your public IP address in the browser this time, not the private one.

  4. Domain buying

    • Buy a domain somewhere. Go to the DNS settings and create an “A” record. Enter your public IP address as the value.
  5. Setting up HTTPS

    • Install the certbot program. Run the following command to generate SSL certificate: sudo certbot certonly -d dissonant.info (obviously, replace the domain name). It will ask you “How would you like to authenticate with the ACME CA?” - just pick the “standalone” option.

    • Note that this software does have the ability to auto-configure Nginx. I would recommend not doing that, because it’s very easy to do manually. You will just need the paths of the generated fullchain.pem and privkey.pem files which are by default in /etc/letsencrypt/live/ - this will be covered below.

  6. Nginx - initial setup

    • Most likely, when you think about “self hosting” you’re not imagining sharing URLs like “123.456.78.9:3000”. Most likely you want to buy a domain name to replace that IP address, and route all traffic through port 80 / 443 (which are the defaults for http:// and https://, respectively). You might have separate websites on my.website/site1 and my.website/site2, or else use subdomains like site1.my.website and site2.my.website. For this we need something called a “reverse proxy” which basically funnels all your local ports (3000, 8080, etc) onto the “normal website” ports 80 and 443. Nginx provides a reverse proxy.

    • Google up on how to install Nginx. For example, see here https://pimylifeup.com/raspberry-pi-nginx/ (ignore the part about PHP setup since I’m assuming you’re not writing PHP).

    • By default, files in /var/www/html are served statically. So if you put a folder “my-website” in there with a file “index.html” in it, you should be able to go to http://my.private.ip/my-website and see the site.

    • Nginx configuration can get complicated, but for now we’re just gonna do all of it in this one file /etc/nginx/sites-enabled/default. You might want to copy the original version of this file in case things get screwed up. Whenever you make a change to this file, you need to run sudo systemctl restart nginx.service for it to take effect.

  7. Nginx - connecting domain

    • Open the Nginx config file I mentioned above. For each distinct domain ("dissonant.info") or subdomain ("blog.dissonant.info") you will need a separate server block, but we will just start with one. Make it look something like this (obviously, replace dissonant.info with your domain.
             server {
           listen 443 ssl default_server;
    
           root /var/www/html;
    
           ssl_certificate  /etc/letsencrypt/live/dissonant.info/fullchain.pem;
           ssl_certificate_key  /etc/letsencrypt/live/dissonant.info/privkey.pem;
    
           ssl_prefer_server_ciphers on;
    
           index index.html
    
           server_name dissonant.info www.dissonant.info;
          }
    
    • Restart Nginx with sudo systemctl restart nginx.service

    • Go into your router’s port forwarding settings and configure the public 80 and 443 ports to go to the Pi’s 80 and 443, respectively.

    • At this point you should be able to visit http://my.domain or https://my.domain and access the files in /var/www/html

  8. Nginx - doing a reverse proxy

    • Say you have an application running on the Pi’s port 3000, and you want to access it through the URL http://my.domain/app1

    • Inside your above server block, add a location block like so:

                 location /app1/ {
                         proxy_pass http://127.0.0.1:3000;
                 }
    
    • Restart Nginx and it should work; however you may encounter issues due to routing - your application expects / as the “root” route, but it’s actually being given as /app1 here - so to make it work correctly, you would need to change your application code to make all links and routes relative to /app1 - kind of a hassle. Using a subdomain is easier. Read on.
  9. Nginx - using a subdomain.

    • Google domains gives unlimited subdomains for every purchased domains; I don’t know if other domain providers do the same, but hopefully most do.

    • To set up a subdomain in the DNS settings is quite easy. Just create another A record with a name such as blog.dissonant.info, and for the value just give your public IP address again.

    • You’re gonna need to create another SSL cert. Use the same command as earlier, e.g. sudo certbot certonly -d jellyfin.dissonant.info

    • Add another server block in Nginx config like so:

         server {
                 listen 443 ssl;
                 ssl_certificate  /etc/letsencrypt/live/jellyfin.dissonant.info/fullchain.pem;
                 ssl_certificate_key  /etc/letsencrypt/live/jellyfin.dissonant.info/privkey.pem;
                 ssl_prefer_server_ciphers on;
    
                 server_name jellyfin.dissonant.info;
                 location / {
                         proxy_pass http://127.0.0.1:8096/;
                 }
         }
    
    • In this example, I have Jellyfin app running on my Pi’s port 8096, and I want that to be accessible through jellyfin.dissonant.info. So you can see, there is both the SSL setup here as well as the reverse proxy (via proxy_pass).

    • Well, that turned out to be quite a mouthful. I certainly hope it works as expected; I haven’t exactly tested that sequence of instructions, but I tried to relay my own setup as best as possible.

  10. Fail2Ban

    • I am not going to include instructions here, but if you use Basic HTTP auth in your Nginx setup, you likely want to set up Fail2Ban to avoid bruce-force vulnerability. This is basically a service which scans your Nginx logs for failed log in attempts and will temporarily ban IP addresses if they do too many, in too short a period.
  11. Some recommendations for things to do with your fancy self-hosting setup:

    • Get a powered USB hub, connect some external hard drives to it, and install Samba; now you’ve got yourself a Network Attached Storage (NAS), This means you can read/write via file explorer on any computer. Note that this does not work over public internet, so there is no point trying to set up port forwarding here. If you want “Access from absolutely anywhere” capabilities, set up FileRun. Note: after using this setup for a while, the slowness got frustrating, and I ended up getting a Synology NAS which makes the whole thing way easier. Highly recommended.

    • Set up Jellyfin to make a self-hosted media server. Point it at your external hard drive (make sure to use a powered USB hub to not overload the PI) and now anyone can stream your movie collection from the web or smart TV. I use this all the time when traveling; I just install Jellyfin app from my AirBnB host’s smart TV, point it to my server, and stream my movies right away!

    • Set up a wiki like this one using Jekyll or Wiki.js

    • Run automated webscrapers using headless Selenium

    • Use Dissonant CMS or some other CMS software to enable collaborative website editing / hosting.

    • … Etc? Feel free to share more ideas.

Thanks for reading and I hope this was helpful! Feel free to contact if there are issues with my instructions, or possibilities for improvement.