Setting Up a Gemini Server

2021-04-18 13 min read technology Otaku Panic

Setting up a server to run a gemini capsule is both easy and rewarding! The setup is pretty fast and when you’re done, you’ll have a shiny new capsule for sharing all of your ideas.

I guess I should recap just in case you’re not familiar with gemini (you could also read my previous article: “An Overall Impression of Gemini”1). Gemini2 is an internet protocol that declares itself lighter than the web and heavier than gopher3 with the intention of replacing neither. Its goal is to exist harmoniously with the other internet protocols and offers a place (called geminispace) that’s inherently secure in a way that gopher wasn’t, and that doesn’t have the bloat and business-centric nature of the web. It’s really a place to go to read about the passions, thoughts, and hobbies of others without the fear of being viewed as a commodity, exploited, and manipulated into purchasing products. It’s … amazing.

So, the purpose of today’s article is to set up a BASIC gemini capsule (the gemini version of a website). Remember, geminispace4 is not the web and you won’t be able to use Firefox, Chrome, or other web browsers to view gemini capsules. For that, you’ll want to use a gemini client like amfora, lagrange, kristall, or myriad others. You can review these on the gemini software page5. I’m going to self-host my capsule using a VPS on Linode6.

Full disclosure: I’m not a professional. I’m not even particularly good at linuxing. So, everything that I’m going to go over works for me, but there are lots of ways to do things and you may prefer something different. That’s your prerogative and I’d be happy to learn new ways to do things if you want to share your methods! I’m just an enthusiast that enjoys doing this - whatever this is. So, grain of salt.

So, I’m going to build an instance on Linode and use that to serve my gemini capsule. You could just as easily use Digital Ocean7 or AWS8. Gemini is text-only and it’s so incredibly light that even the base servers are going to be overkill to serve a gemini capsule. I went with the $5.00/month Nanode on Linode. The biggest decision you’re going to have to make is what distro you want to use. Now, I ended up with analysis paralysis because I had two distros in mind that were going to be easy in different ways. The first option was Fedora, which is what I run on my production machine and I am intimately familiar with it. Perhaps I’ll do a blog post on distros and tell you why I love Fedora. But that’s a sidenote. The second option was Arch and that has everything to do with visiting the github page of the gemini server I wanted to use.

So, Agate9 is a gemini server that’s written in Rust. It’s very simple, very light, very easy to use, and perfect for my use case. In fact, it seems to be the preference for a lot of people out there that are talking about gemini. Or, at least the ones that I know about. So, I went to the github page for Agate and noticed that it had an AUR package. Previous to seeing this I was going to run Fedora - and why wouldn’t I? I’m familiar with how it works and feels, its packages, how it deals with security, etc. But … Agate as a binary from the AUR? I mean, I know it’s not even hard to install Agate even if you’re not getting it from the AUR, but it’s just so easy when it is in the AUR. And then suddenly my mind made a decision and I picked “Arch Linux” from the drop-down menu.

And, I know this means that I’ll have to update more often. But, it’s a gemini capsule, and Arch will update in 5 minutes. I think a server downtown of 5 minutes per week is acceptable for my use case.

On Linode, that’s basically it. You choose the system resources you want, which dictate the price, and tell it which distro to install. You give your server a name and root password (make it a good one) and suddenly you have a server. Optionally, you can upload an ssh key to your Linode account and it will preinstall your key onto your server. You don’t have to do this, but I would recommend it. For sure, you’ll need to set up ssh keys once your server is running even if you don’t want to upload it to your Linode account (why would you not…?). Keys are easier, faster, more secure.

Anyway, I’m not going over details about setting up a VPS because that’s just going to depend on your provider, and like I said, for this deployment it doesn’t really matter which reputable provider you choose. They all have good documentation if you’re not sure about something.

But, once you have an instance running, I can give you a brief overview of some reasonable setup options. Remember there is no such thing as an unhackable server. If it’s connected to the public internet it can be hacked. To be fair, if it’s connected to anything (I’m looking at you Bluetooth) it can be hacked. Hackers are usually exceptional people, so “hack proof” isn’t an option. But, we can make our server undesirable by making reasonable security decisions. A gemini server doesn’t really have anything to offer anyone as far as user accounts and access to sensitive records - that’s just not the type of information that you’re likely to host. So, it’s already not an ideal target, we just need to build on that.

First, ssh into your new server using root:

$ ssh root@server_ip_address

I would create a user to deal with your gemini stuff, I would not continue to use root indefinitely. There are a lot of flags you could pass, but it can just be as easy as:

# useradd -m -G wheel name_of_user

The -m is going to create a home directory, -G will add the user to a group that you’re going to say is “wheel,” and the name of the user is whatever you want the username to be. Adding your user to the wheel group makes it easy to make them a sudoer (giving them the ability to exercise root-level commands). You can choose to grant permissions in a more modular fashion, but this situation doesn’t really call for that.

Next, give your new user a password:

# passwd name_of_user

Enter your password. I strongly recommend very strong, randomly generated root and user passwords. This is a great time for a password manager like BitWarden10 - or Lastpass, or KeePass, etc.

Now, you need to make yourself a sudoer! Traditionally, the command to edit your sudoers file is going to use vi text editor, but if you’re not a vi(m) person (if you don’t know, then you aren’t), you can pass it an easier editor. I’ll show you how to edit it using nano because most people think nano is easier.

# EDITOR=nano visudo

It’s going to open your sudoers file in nano and you need to find the following line:

# %wheel ALL=(ALL) ALL

You’ll uncomment it so it looks like this:

%wheel ALL=(ALL) ALL

(Is this necessary and helpful? I’m not sure anymore. The original intention of this blog post wasn’t to be this broken-down.)

Save it (ctrl+o) and exit (ctrl+x) and you’re a sudoer.

Switch to your new user:

# su name_of_user 

And run something as sudo. I recommend updating! It’s a good way to test. Run:

$ sudo pacman -Syu

Enter your user’s password when it asks. If that went well, then you’re a sudoer.

If you didn’t create an ssh key for root, then we’re going to do that now for your user. Exit your ssh session. You need to do this on your local machine. Run:

$ ssh-keygen -t rsa -b 4096 -f path/to/key

Your path is most likely going to be ~/.ssh/name_of_file (I have too many keys - I name them). Example:

$ ssh-keygen -t rsa -b 4096 -f ~/.ssh/arch_server_rsa

Password protect it if you want by completing the password field (you can leave it blank for no password). A password is more secure as it will require you to unlock your key whenever you try to use it to ssh into your server. This password is to unlock your key - it’s not the password to your server.

The -t is type and RSA is very standard, -b is the size and 4096 is considered strong, and the -f is to pass the location and key name.

Two files were generated when you ran ssh-keygen. The first was (keeping with the example) arch_server_rsa and the second was You need to move the *.pub to your server. It is important to specify that correctly.

Move the key to your server:

$ ssh-copy-id -i ~/.ssh/ name_of_user@server_ip_address

Now, try to login to your ssh session again and it shouldn’t ask you for a password (unless you password protected your ssh key and need to provide the ssh key’s password to unlock it).

$ ssh name_of_user@server_ip_address

You can check the authorized hosts file on your server to see your public key!

$ ssh nano ~/.ssh/authorized_keys

Now we get to start hardening the server a little bit. We’ve enabled an ssh key to log in, which is a more secure method than a password. Now we can make some adjustment to our ssh configuration to lock it a down a little more.

$ sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.old

This is so you can replace the file if you break in the future. I break things, so this is a necessity for me.

You can use whatever editor you want, but I’m just going to stick with nano because a lot of people think it’s more straight forward.

$ sudo nano /etc/ssh/sshd_config

Find the line for “port” and change it. The default is 22 so that’s where a bad actor is likely to start when they’re testing a server for access. Choose any non-standard port that isn’t used by another service (if you’re only going to be running a gemini server, then it’s almost any other port). I’ll just say port 400.

# /etc/ssh/sshd_config

Port 400

Continue by uncommenting these lines and assigning these values:

# /etc/ssh/sshd_config

PermitRootLogin no
PasswordAuthentication no
ChallengeResponseAuthentication no

You need to restart sshd for changes to take effect.

$ sudo systemctl restart sshd

This is going to prevent anyone without the ssh key you created earlier from logging in. You will no longer be able to use a password to authenticate. You’ll also be preventing root from ever being able to login, which means that no one using a bruteforce would have a known user to test against. You could go futher and install something like fail2ban so if you do find yourself enabling password authentication, there would be limits on login attempts, but I won’t go over that.

You’ve also changed your port, so you will now need to define your port when you try to ssh into your server. In the example above, you’d specify port 400.

$ ssh -p 400 name_of_user@server_ip_address

The -p is so you can define the port number.

Now that the ssh settings are reasonably good, we can move on to the next step. The whole reason I decided to use Arch Linux was because there was a precompiled binary in the AUR for Agate. So, we need to get an AUR helper to install it. The most popular is “yay” but gaining in popularity is “paru.” Either one will work and follows the same process. You will need the tools in base-devel to use an AUR helper.

$ sudo pacman -S base-devel

I also usually make a directory to store things I get through git, but if you choose not to do that, there will just be a folder in your home directory.

$ mkdir git
$ cd git/
$ git clone
$ cd yay/
$ makepkg -si

That will install yay. You could change git clone to git clone and you’d have paru.

Now to install something from the AUR (Agate in our case) you would just use yay or paru instead of pacman. You don’t want to use sudo with an AUR helper until it’s time to install whatever you compile. Let’s get Agate.

$ yay -S agate-bin

I got the precompiled binary, but you can compile it yourself by just typing “agate.” Be warned - compiling on a low-powered VPS is going to take a billion years.

$ yay -S agate

When you run Agate for the first time, it will create your certificates for you auto-magically. So, let’s create some places for our site to live.

$ mkdir ~/mysite/
$ mkdir ~/mysite/content/

So, you can be up and running just by passing a few options to Agate OR you can update the configuration file so you just have to call Agate. Let’s update the file. We’ll copy the configuration file just in case we break it in the future.

$ sudo cp /etc/agate/agate.conf /etc/agate/agate.conf.old

Then we can update the file:

$ sudo nano /etc/agate/agate.conf

Initially it will look like this:

# See agate homepage

CONTENT=--content /var/agate/content/
CERT=--certs /var/agate/.certificates
ADDR=--addr [::]:1965 –addr
LANG=--lang en_US
#OTHERS=--central-conf --only-tls13


The “CONTENT” line is where Agate will read your content. The “CERT” line is where it will read your certificates. The “HOSTNAME” line is whatever domain you’ll be pointing to your gemini capsule. Just enter an A record to the IP address of your server.

To match the example, make these changes:

# See agate homepage

CONTENT=--content /home/name_of_user/mysite/content/
CERT=--certs /home/name_of_user/mysite/.certificates
ADDR=--addr [::]:1965 –addr
LANG=--lang en_US
#OTHERS=--central-conf --only-tls13


$ agate &

Agate will run and start serving the capsule.

In the future, when you update and restart your server, you’ll just start Agate again by running:

$ agate &

And it will start back up as a background process. I have seen agate configured to work with systemd11, so that might be something to consider. It’s unnecessary for me.

Now you can start adding content!

Create an “index.gmi” file in /home/name_of_user/mysite/content, which will act as your gemini capsule’s home page.

The following formatting is acceptable:

Preformatted text

# Level One Header
## Level Two Header
### Level Three Header

=> link/to/another.gmi “Name of Link that Shows in the Client”

And that should get you started. You can nest folders under content/ for new pages that you create.

For additional security, I would just verify that the only open port was Agate’s.

$ sudo netstat -tulnp

It might show some systemd stuff that’s being used to resolve, but that would be fine. If you’re going to be running other services that are going to open up ports and be interacting with each other, then I would definitely set up firewall rules through iptables, or firewalld, and initiate other security best-practices. There would be additional server hardening you’d want to pursue. And, it’s actually really fun to learn and experiment. But just for a single gemini capsule, this should get you started.

Hope you got something from this!

Attribution: Photo by Kelvin Ang on Unsplash