TomCon.net Infrastructure

TomCon.net is my first ever website. I have never self-hosted an external service like this before. Getting to this point where text is rendering on your screen took a decent amount of learning and trial and error. Before this, I was vaguely familiar with some computer networking essentials like internal and external IPs and forwarding port numbers. I have been using Linux Mint as a daily driver for a few years, so I know the basics of UNIX file structure and how to work a terminal.

This site is hosted on a ThinkPad T440s running Linux Mint 22.1 Cinnamon. The laptop was a spare I had lying around, and I finally put it to good use.

What You'll Need

Estimated Time

General Steps to Get to This Point

Step 1: Buy A Domain Name

I think you should start with buying a domain name as your first step. It's useful to know going into this what your domain name is going to be. There are many providers that are suggested. I chose epik.com as a domain name provider, but you can go with Cloudflare, Namecheap, and many others.

I now pay $15 per year for the privilege of renting a human-readable name certified by a domain authority that gets tied to my network's external IP address. It's unfortunate I can't self-host the domain registry process too. For now, I'm stuck with renting a name from an authority and using their web services to configure things. It's a necessary evil you have to do to have a website.

Note that other network protocols for sharing information like I2P and Tor do away with the need for a domain name system, I believe.

Tie Domain Name with Server External IP Through Provider's Web Interface

When you find out your external IP with whatsmyip.com, go into your domain name provider's web interface and find 'DNS Host Records'. Here, make sure your domain and subdomains (with the A and AAAA records) point to your server's external IP. Do the same process for all subdomains you want to create, as they all take the exact same pointed IP number configuration.

Troubleshooting tip: Check if the domain name service tried to automatically put in a trailing colon after you put in your public IP. For some reason, mine gave the option and the added colon messed things up.

Chapter 1: Foundation - OS Install and Web Server Setup

This is the core step of setting up my website. A good operating system like Linux Mint provides an out-of-the-box beginner-friendly ecosystem that's easy to install, offers bulletproof stability, and has good community support with over a decade of forum activity and a very active IRC to fall back on. Note that other operating systems are purpose-built as hardened and secured headless server OS. At some point, I may feel the need to switch to one.

Setup Linux Mint

IP Information Gathering

Setup Backup

Install Apache2

Apache is a simple web page server. A client requests connection to Apache, then Apache sends HTML page information to a receiving client on an open port.

sudo lsof -i :80

Add Apache2 to systemctl and Enable It

To use a terminal to connect to the webpage and pull the index HTML file:

curl -I http://localhost

Add Your Domain Name to the Hosts File

This is so you can use your domain name in a local network client's web browser without SSL errors:

sudo nano /etc/hosts

Troubleshooting Local Clients Connecting to Apache

Another potential option, especially if troubleshooting and the domain name isn't working for whatever reason, is to use direct local IP and port numbers. Enter into your browser `http://10.0.0.xx`, `http://10.0.0.xx:8080`, or `http://localhost:8080` into the address bar. You can try undoing the port change from 8080 back to 80 and connecting without defining the port after restart (your web browser automatically requests HTTP on 80 and HTTPS on 443). You can also try allowing 8080 through the firewall if it's already set up. The goal is just to see if Apache is running and serving the default page, and if you can connect locally.

Apache Configuration

Located in `/etc/apache2/ports.conf`:

# If you just change the port or add more ports here, you will likely also
# have to change the VirtualHost statement in
# /etc/apache2/sites-enabled/000-default.conf

Listen 8080

<IfModule ssl_module>
#	Listen 443
</IfModule>

<IfModule mod_gnutls.c>
#	Listen 443
</IfModule>

Moving Forward

Make sure Apache is running and you can connect locally. If so, it's time to move on to exposing the service to external client connections. It's heavily recommended that you follow the next steps for security hardening and reverse proxy with Caddy. It is entirely possible for you to just serve regular unencrypted HTTP at this point though. Port forward 80/TCP of your server's local IP in the gateway or router settings and point the domain name at your network's external IP address. Adjust security headers through Apache. External clients should be able to receive HTML pages using your domain name through HTTP, but they'll receive warnings every time they connect and your SEO will suffer. It's better for everyone involved to set up a way to handle SSL and TLS certificates.

Chapter 2: Security Part 1 - Setup Caddy for HTTPS and Reverse Proxy for Local Network

Caddy serves two important roles along with Apache.

First, it acts as a simple and automatic TLS and SSL encryption certificate server. This enables your website to serve HTTPS instead of just HTTP, which provides a massive security boost, good SEO ranking, and no scary client browser warnings about unencrypted connections.

Second, it acts as a reverse proxy. In non-technical terms, a reverse proxy acts as a bouncer or middleman to all external client connections trying to connect to your services through the gateway (your ISP router) on forwarded open ports. It's not clear at first why this is important, until you want to host multiple services or servers on different computers connecting to one domain name through subdomains.

A reverse proxy is the solution to two problems at once. It handles pairing an external subdomain with a specific local IP. And if some of these external services send out web content as HTTP but have no way of issuing SSL certificates, it's not a problem. The reverse proxy handles that part of the connection in the local service's place. The reverse proxy part is a little complicated to understand, but it becomes clear what it does the more you think about it (which is true of most things, I guess). It's an important part of modern internet infrastructure that helps give your network a boost in security and configurability.

I chose Caddy as a reverse proxy and HTTPS encryption certificate server instead of nginx and certbot because I was told it was easier to configure. Caddy has automatic HTTPS already setup. The Caddyfile config promised to not be a nightmare slog to read through or configure from scratch. I'm not used to this kind of thing, so I went with the option that promised easiest implementation with the essential features I really needed.

Nginx has some things out of the box that Caddy doesn't, but by the time I get to needing them, I'll probably be comfortable enough to wrangle with configs without getting stressed out. At the end of the day, I wanted to get something working and start making content, not wrangle with granular infrastructure configs for hours just for something to break.

Installing Caddy

Caddy requires debian keyring and curling apt repositories before installation with `sudo apt install caddy` on Linux Mint.

curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install caddy

Caddyfile Configuration

This is the real Caddyfile I am using for my site tomcon.net. It took a bit of figuring out on my part how to properly format everything. The Caddy docs help but not enough. I hope sharing this serves you well in your own site-building journey.

My Caddyfile for tomcon.net

# The Caddyfile is an easy way to configure your Caddy web server.
#
# Unless the file starts with a global options block, the first
# uncommented line is always the address of your site.
#
# To use your own domain name (with automatic HTTPS), first make
# sure your domain's A/AAAA DNS records are properly pointed to
# this machine's public IP, then replace ":80" below with your
# domain name.

tomcon.net {
	header {
		# Content Security Policy (adjust to your needs)
		Content-Security-Policy "
        default-src 'self';
        script-src 'self';
        style-src 'self';
        img-src 'self' data:;
        font-src 'self';
        connect-src 'self';
        object-src 'none';
        frame-src 'none';
        upgrade-insecure-requests;
    "

		# XSS Protection
		X-XSS-Protection "1; mode=block"

		# Prevent MIME sniffing
		X-Content-Type-Options "nosniff"

		# Frame options
		X-Frame-Options "SAMEORIGIN"

		# Referrer policy
		Referrer-Policy "strict-origin-when-cross-origin"

		# Feature policy (now mostly replaced by Permissions-Policy)
		Permissions-Policy "geolocation=(),midi=(),sync-xhr=(),microphone=(),camera=(),magnetometer=(),gyroscope=(),fullscreen=(self),payment=()"

		# Remove server header
		-Server
	}
	header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"

	# Set this path to your site's directory.
	root * /var/www/html

	# Enable the static file server.
	file_server

	# Another common task is to set up a reverse proxy:
	reverse_proxy localhost:8080

	# Or serve a PHP site through php-fpm:
	# php_fastcgi localhost:9000
	tls my-email@redacted.com
}

ai.tomcon.net {
	tls my-email@redacted.com
	reverse_proxy http://10.0.0.53:5005
	header {
		# Content Security Policy (adjust to your needs)
		Content-Security-Policy "
        default-src 'self';
        script-src 'self';
        style-src 'self';
        img-src 'self' data:;
        font-src 'self';
        connect-src 'self';
        object-src 'none';
        frame-src 'none';
        upgrade-insecure-requests;
    "

		# XSS Protection
		X-XSS-Protection "1; mode=block"

		# Prevent MIME sniffing
		X-Content-Type-Options "nosniff"

		# Frame options
		X-Frame-Options "SAMEORIGIN"

		# Referrer policy
		Referrer-Policy "strict-origin-when-cross-origin"

		# Feature policy (now mostly replaced by Permissions-Policy)
		Permissions-Policy "geolocation=(),midi=(),sync-xhr=(),microphone=(),camera=(),magnetometer=(),gyroscope=(),fullscreen=(self),payment=()"

		# Remove server header
		-Server
	}
	header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
}

Where Caddy Stores TLS and SSL Certificates

Caddy stores a cert and key pair for each registered domain and subdomain here:

/home/caddy/.local/share/caddy/certificates/acme-v02.api.letsencrypt.org-directory

Chapter 3: Port Forwarding

Once you have Caddy and Apache2 setup, go into your router and find the port forwarding configuration settings. Make note of your Caddy and Apache server's local IP address, and forward the ports 80/TCP and 443/TCP. Save these settings. These are the standard ports for HTTP and HTTPS which web browsers quietly knock on to connect to. You've just exposed them for external connection. Instead of directly connecting to Apache, Caddy claims these ports in Apache's place so all external connections go through Caddy first. Apache sends HTML to Caddy on port 8080, Caddy adds certificates and passes the data to the external client. Neat!

Chapter 4: Security Part 2 - Hardening Website Headers

Once your site has HTTPS and is externally connectable, go to securityheaders.com and Mozilla Observatory to get your site tested for header hardening. You want as high a score as you can manage. The headers included in the Caddyfile I provided are enough to get tomcon.net a near-perfect score.

Useful Resources:

Chapter 5: Security Part 3 - System Hardening

Configure Firewall

Allow ports in UFW:

sudo ufw allow http
sudo ufw allow https
sudo ufw allow 80
sudo ufw allow 443

Reload and enable UFW to activate your firewall:

sudo ufw reload
sudo ufw enable
sudo ufw reload

Add UFW to systemctl and enable:

sudo systemctl enable ufw

Install fail2ban and Enable in systemctl

Fail2ban helps deal with potential issues related to brute force attacks. It monitors connections and if one client tries to connect too many times too quickly, it triggers a temporary ban or 'jails' the IP for a time.

sudo apt install fail2ban
sudo systemctl enable fail2ban
sudo systemctl start fail2ban

Common Troubleshooting Issues

Caddy Won't Start

Can't Connect Externally

Certificate Issues

APPENDIX: Related Bash History of Setting Up tomcon.net

This is the entire related bash history containing all the terminal commands I put in up to the point the site came online. This doesn't reflect everything done, as some actions were through the GUI like editing configs. Nonetheless, it should help give you an idea of the simple workflow and testing it took to set up tomcon.net from a base Linux Mint Cinnamon install.

    1  ifconfig
    2  sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
    3  curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
    4  curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
    5  sudo apt update
    6  sudo apt install caddy
    7  sudo nano /etc/caddy/Caddyfile
    8  sudo systemctl reload caddy
    9  sudo systemctl enable caddy
   10  sudo systemctl status caddy
   11  sudo systemctl reload caddy
   12  sudo systemctl status caddy
   13  sudo ufw allow http
   14  sudo ufw allow https
   15  sudo ufw allow 80
   16  sudo ufw allow 443
   17  sudo ufw reload
   18  sudo ufw enable
   19  sudo ufw reload
   20  sudo caddy validate --config /etc/caddy/Caddyfile
   21  sudo systemctl reload caddy
   22  sudo systemctl status caddy
   23  sudo caddy validate --config /etc/caddy/Caddyfile
   24  caddy fmt --overwrite
   25  caddy fmt --overwrite /etc/caddy/caddyfile
   26  caddy fmt --overwrite etc/caddy/caddyfile
   27  caddy fmt --overwrite etc/caddy/Caddyfile
   28  caddy fmt --overwrite /etc/caddy/Caddyfile
   29  sudo systemctl reload caddy
   30  sudo caddy validate --config /etc/caddy/Caddyfile
   31  curl -I http://localhost
   32  sudo systemctl status caddy
   33  sudo systemctl reload caddy
   34  sudo systemctl status caddy
   35  sudo systemctl reload caddy
   36  sudo systemctl status caddy
   37  sudo systemctl reload caddy
   38  nano /etc/host
   39  sudo nano /etc/host
   40  sudo nano /etc/hosts
   41  sudo systemctl reload caddy
   42  sudo systemctl status caddy
   43  sudo systemctl reload caddy
   44  sudo systemctl status caddy
   45  sudo systemctl reload caddy
   46  sudo systemctl status caddy
   47  sudo apt install apache2
   48  sudo systemctl enable apache2
   49  sudo systemctl status apache2
   50  sudo systemctl enable apache2
   51  sudo systemctl start apache2
   52  sudo systemctl status apache2
   53  systemctl reload caddy
   54  sudosystemctl reload caddy
   55  sudo systemctl reload caddy
   56  sudo systemctl status caddy
   57  sudo lsof -i :80
   58  sudo nano /etc/hosts
   59  sudo systemctl status caddy
   60  ifconfig
   61  sudo systemctl reload caddy
   62  sudo systemctl status caddy
   63  sudo ufw allow 8080
   64  sudo systemctl reload caddy
   65  sudo systemctl status caddy
   66  sudo lsof -i :80
   67  sudo lsof -i :8080
   68  sudo systemctl status caddy
   69  sudo journalctl -u caddy --since 10 minutes ago
   70  sudo journalctl -u caddy --since "10 minutes ago"
   71  sudo journalctl -u caddy
   72  sudo systemctl reload apache2
   73  sudo a2enmod headers
   74  systemctl restart apache2
   75  sudo systemctl  apache2
   76  sudo a2enmod headers
   77  sudo systemctl restart apache2
   78  sudo systemctl reload apache2
   79  sudo systemctl restart apache2
   80  sudo systemctl restart caddy
   81  sudo systemctl reload caddy
   82  sudo systemctl restart caddy
   83  sudo systemctl reload caddy
   84  sudo systemctl restart caddy
   85  sudo systemctl reload caddy
   86  sudo systemctl status caddy
   87  sudo systemctl reload caddy
   88  sudo systemctl status caddy
   89  sudo systemctl restart caddy
   90  sudo systemctl status caddy
   91  sudo systemctl reload caddy
   92  sudo caddy validate --config /etc/caddy/Caddyfile
   93  caddy fmt --overwrite /etc/caddy/caddyfile
   94  caddy fmt --overwrite /etc/caddy/Caddyfile
   95  sudo caddy validate --config /etc/caddy/Caddyfile
   96  sudo systemctl reload caddy
   97  sudo systemctl restart caddy
   98  sudo systemctl reload caddy
   99  sudo systemctl status caddy
  100  sudo systemctl reload caddy
  101  sudo systemctl restart caddy
  102  sudo systemctl status caddy
  103  sudo journalctl -u caddy --since 10 minutes ago
  104  sudo journalctl -u caddy --since "10 minutes ago"
  105  sudo apt install fail2ban
  106  sudo systemctl restart caddy
  107  sudo systemctl reload caddy
  108  sudo journalctl -u caddy --since "5 minutes ago"
  109  sudo systemctl restart caddy
  110  sudo systemctl reload caddy
  111  sudo journalctl -u caddy --since "5 minutes ago"
  112  caddy fmt --overwrite /etc/caddy/Caddyfile
  113  sudo systemctl restart caddy
  114  sudo systemctl reload caddy
  115  sudo caddy validate --config /etc/caddy/Caddyfile
  116  cat ~/.bash_history
  117  sudo nano /etc/hosts
  118  sudo systemctl enable fail2ban
  119  systemctl start fail2ban
  120  sudo systemctl status caddy
  121  sudo journalctl caddy
  122  sudo journalctl -u caddy | grep -i acme
  123  sudo journalctl -u caddy
  124  systemctl status ufe
  125  systemctl status ufw
  126  sudo ufw enable

Gemini Infrastructure

The Gemini protocol aspect of this site is powered by a simple Agate server written in Rust. Gemini is a small-net protocol designed for simplicity in implementation and text formatting. It may be accessed using native browser software or web-based proxies.

Agate Install

Visit Agate's git repository and download the precompiled binary. Put the program into `/bin` and open a new bash instance. Run the following:

sudo agate --hostname your-domainname.com --addr 10.0.0.xx:1965 --content /home/user/gemini/gemtext

If all goes well, the Agate server should start up fine. Agate will generate some TLS certificates in a hidden folder in the user home directory `.certificates` in the .DER key and cert pair format.

Note: Remember to forward port 1965 in your router settings if you want external access to your Gemini capsule.