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.
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).
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.