Using nginx as a forward proxy for gemini

Accessing gemini without SSL support
The gemini ‘smallweb’ protocol relies on SSL support for security, encryption, etc. The content of these gemini capsules is almost entirely text-only, ideal for browsing on limited or old hardware, such as my Amiga. Although SSL support is possible on more powerful Amigas, mine can’t actually load the SSL library and use a network connection simultaneously, so I wanted a solution to get gemini capsules displaying without SSL. My solution is to use nginx as a forward proxy. The Amiga (or other device) sends an unencrypted request to the proxy, which then wraps it in a warm security blanket and posts it out onto the internet, and then unpackages the secured return traffic for the Amiga too.
Stream
nginx doesn’t really ‘support’ gemini, but it has a generic ‘stream’ module that can handle weird traffic. My local nginx.conf now includes this
stream {
# DNS resolver
resolver 1.1.1.1;
# Import the JS module
js_import gemini from gemini_proxy.js;
#gemini_host variable will contain the name of the host we want to connect to
js_set $gemini_host gemini.get_host;
server {
listen 8888; # Old machines connect on this port
# Use the imported javascript to sniff the hostname from the incoming request
js_preread gemini.preread_gemini;
# Proxy the request to the host on the standard gemini port
proxy_pass $gemini_host:1965;
proxy_ssl on;
proxy_ssl_server_name on;
proxy_ssl_protocols TLSv1.2 TLSv1.3;
proxy_ssl_verify off;
}
}
And the helper js file gemini_proxy.js
var host = '';
function preread_gemini(s) {
s.on('upload', function (data, flags) {
if (data.length > 0) {
// Find the Gemini URL in the raw text payload
// Matches 'gemini://domain.com' or just 'domain.com'
var match = data.match(/gemini:\/\/([^\/\:\r\n]+)/) || data.match(/^([^\/\:\r\n]+)/);
if (match) {
host = match[1];
s.log("Extracted Gemini Host: " + host);
s.done(); // Stop prereading and move to next phase
}
}
});
}
function get_host(s) {
return host;
}
export default { preread_gemini, get_host };
Since the stream module can’t tell where the traffic is supposed to go (it’s just a random stream after all), the javascript scans the incoming stream and tries to locate the destination url. This is used to populate the $gemini_host variable in nginx, which then allows the proxy_pass directive to.. proxy.
Security and safety considerations
Lol no. Don’t host open proxies unless you understand all the implications, even if it’s just for gemini.
Unneccessary?
Karl Jeacle, the author of AmiGemini hosts a public proxy free for everyone to use. He even supplies a python script to run your own if you don’t want to connect to his instance. However, I already have an nginx instance running on my network and thought this would be an ideal use for it. But if you’re in the highly normal situation of running an Amiga without SSL and using it to browse Gemini capsules, and you don’t have an nginx instance, his proxy is probably the best bet.
Acknowledgements
I needed assistance extracting the server name from the stream, and got help from Sid (why do you not have a blog?). Then while writing up this blog post he informed me that the javascript was AI generated. Amazing. Now I’m sullied and impure. I don’t like to use AI, and now I’ve used it by proxy.. to build a proxy?