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.
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.
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.
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.
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
To use a terminal to connect to the webpage and pull the index HTML file:
curl -I http://localhost
This is so you can use your domain name in a local network client's web browser without SSL errors:
sudo nano /etc/hosts
10.0.0.XX yourdomain.com www.yourdomain.com
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.
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>
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.
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.
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
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.
# 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"
}
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
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!
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:
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
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
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
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.
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.