Gemini is a simple protocol and markup for transmitting text pages on the internet. It is similar to http/html and improves on gopher.

With, one can surf gemini pages with a web browser.

I use gemini for the following reasons:

  • the documents don't execute code
  • there is minimal tracking (only ip address)
  • the clients are lightweight and will that way
  • the servers are lightweight
  • I don't need to get a certificate from let's encrypt for my personal sites
  • I can write my own server


On my phone, I tried:

  • Elaho: iOS, good
  • Deedum: iOS and Android, good

On the terminal, I used ncat:

apt-get install ncat
echo -ne 'gemini://\r\n'|ncat --ssl 1965

The amfora tui client is also good.

Markdown to Gemini markup converter

I tried md2gemini, to install it, run:

apt-get install python3-pip
pip3 install md2gemini

To convert a markdown file, run:

md2gemini > file.gmi

Setting up a Gemini server

I chose to run the gmnisrv gemini server, because it is lightweight and simple to build. It only depends on the openssl library.

I run the following commands:

git clone
sudo apt-get install libssl-dev
cd gmnisrv
mkdir build
cd build
sudo make install

I keep the default configuration, so everything is installed in /usr/local/

I create a home directory for gmnisrv:

mkdir -p /home/user/gemini/certs /home/user/gemini/root

I generate a gemtext file with md2gemini:

md2gemini > ~/gemini/root/index.gmi

I copy the default configuration file:

sudo cp /usr/local/share/gmnisrv/gmnisrv.ini /usr/local/etc/gmnisrv.ini

I edit the config file:

sudo vi /usr/local/etc/gmnisrv.ini

The default config file looks like this:

# Space-separated list of hosts
listen= [::]:1965

# Path to store certificates on disk

# Optional details for new certificates
organization=gmnisrv user


I change the tls store path, the virtual server(to my local ip address) and the root for the virtual server:

# Space-separated list of hosts
listen= [::]:1965

# Path to store certificates on disk

# Optional details for new certificates
organization=gmnisrv user


Then I start the server and test it:

echo -ne 'gemini://\r\n'|ncat --ssl 1965

I also tried the Moongem server, there is no need for a configuration file. The program takes certificate, key and document root in arguments.

Here is how to build it in debian bullseye:

apt-get install libmagic-dev libssl-dev liblua5.4-dev cmake
git clone
cd MoonGem && mkdir build && cd build
cmake ..

It doesn't generate a certificate automatically, so I generate one with the command (replace 'URL' with the server DNS address):

openssl req -new -x509 -days 365 -nodes -out cert -keyout key -subj "/CN=URL" -newkey rsa:4096 -addext "subjectAltName = DNS:URL"

To start the server, run:

./moongem cert key .

The gmid gemini server works fine but it doesn't send the images on my gemini pages.

Gemini markup: Gemtext

Here's the basics of how text works in Gemtext:

  • Long lines get wrapped by the client to fit the screen
  • Short lines don't get joined together
  • Write paragraphs as single long lines
  • Blank lines are rendered verbatim

You get three levels of heading:

# Heading

## Sub-heading

### Sub-subheading

You get one kind of list and you can't nest them:

* Mercury
* Gemini
* Apollo

Here's a quote from Maciej Cegłowski:

> I contend that text-based websites should not exceed in size the major works of Russian literature.

Lines which start with ``` will cause clients to toggle in and out of ordinary rendering mode and preformatted mode. In preformatted mode, Gemtext syntax is ignored so links etc. will not be rendered, and text will appear in a monospace font.

Write links like this:

=> URL Title
=> gemini:// Remy Noulin's Homepage

CGI configuration for gmnisrv

This section shows an example setup of cgi scripts, for more information read the man for gmnisrvini (install the scdoc package to convert gmnisrvini.scd to roff: scdoc < gmnisrvini.scd > gmnisrvini.roff && man -l gmnisrvini.roff)

  • Create a route for storing the gemtexts where cgi is off. The files in /srv/gemini/ are served according to their mime types.
  • Create a route for the executable programs where cgi is on. The files in /srv/gemini/ are executed and everyting printed to stdout is sent as a response to the client.
  • Start gmnisrv

Create the script and make the script executable with chmod 755

echo "20 text/gemini\r\n"
echo "REMOTE_USER " $REMOTE_USER print the environment variables set by gmnisrv.

Now it is possible to call

echo -ne 'gemini://\r\n'|ncat --ssl 1965
echo -ne 'gemini://\r\n'|ncat --ssl 1965
echo -ne 'gemini://\r\n'|ncat --ssl 1965
  • On the first request, PATH_INFO and QUERY_STRING are empty strings
  • On the second request, PATH_INFO is an empty strings and QUERY_STRING equals to Text 1024 Bytes Max. The %20 strings in the query string are converted to space characters.
  • On the third request, PATH_INFO is set to hello and QUERY_STRING is set to world.

To receive an input from client, the script should respond with 10 Enter your text:\r\n instead of 20 text/gemini\r\n.

When the client sends the input, the script is called again and QUERY_STRING is the input from the client. Eventually the script responds with 20 text/gemini\r\n+gemtext to show the result of the client request.

To make a gemcall to an input endpoint (for example:, run this:

echo -ne 'gemini://\r\n'|ncat --ssl 1965