Introducing Mineshaft: a reasonably incompetent Gemini server

Written on 06.01.2026

I got bored one afternoon and I thought to myself: "how hard would it be to make a Gemini server?". To make it a bit harder, I decided to use Ruby instead of my go-to language for these projects, Python. Now this is not intended to replace any well-estabilished Gemini server software you might use, in fact, I am probably going to abandon this project. It was a fun learning exercise, and unless you fancy using some really crappy code to serve your Gemini site, you should not use this.

What the hell is Gemini?

Gemini is a new application-layer internet protocol for accessing remote documents. It is very similar to the HTTP and Gopher protocols. It was started in 2019 and currently, according to the Kennedy search engine, the Gemini network contains 3,284 active servers (i.e. Capsules), 488,423 total URLS and 433,386 documents (as of 12/6/2025 4:01:33 PM).

Basic Gemini "Capsule"

It's trivially simple to make a socket in Ruby:

# Taken from https://docs.ruby-lang.org/en/master/Socket.html
require 'socket'

server = TCPServer.new 2000

loop do
  client = server.accept
  client.puts "Hello !"
  client.puts "Time is #{Time.now}"
  client.close
end

Now if you were to start serving Gemini stuff over it, you will encounter three problems: you don't have TLS, you aren't getting the client's input and you aren't sending proper Gemtext files!

Fixing these issues is trivial, except for TLS, for which I followed this pretty good tutorial: https://workingwithruby.com/wwtcps/ssl/.

require 'socket'
require 'openssl'

server = TCPServer.new 1965
context = OpenSSL::SSL::SSLContext.new
# You'll need to generate a self-signed TLS certificate
# openssl req -x509 -sha256 -nodes -days 3650 -newkey rsa:2048 -keyout server.key -out server.crt
context.cert = OpenSSL::X509::Certificate.new(File.open("./server.crt", "r").read())
context.key = OpenSSL::PKey::RSA.new(File.open("./server.key", "r").read())
context.verify_mode = OpenSSL::SSL::VERIFY_PEER
ssl_server = OpenSSL::SSL::SSLServer.new(server, context)

loop do
    client = ssl_server.accept
    client.gets
    client.print "20 text/gemini\r\n"
    client.print "# Test"
    client.close
end

From here you can start adding in error handling, threading, configuration loading, etc., which is what I have done with Mineshaft.

Anyways, if you really want to use Mineshaft, the source code is available here.

Unless noted otherwise, all the content on this page is licensed under CC0.
"Life in liberty or death"