HoTCo:RE
The easiest, simplest way to serve multiple domains from the same server
- Painless install
- Zero configuration
- Out of the box HTTPS
Introduction / Motivation
HoTCo:RE is an HTTP(S) reverse proxy that routes requests based on the domain name.
You have multiple web apps, each listening on a different port, but you want to serve them all from the same machine, and you want each one to respond to its own domain name.
App A is listening on port 8080, but you want to serve it on https://a-app.com
.
App B is listening on port 9000, but you want to serve it on https://b-app.com
.
You’ve got your DNS entries set up for the domain names to point to your server. Now what?
You could set up something like nginx, HAProxy, or Caddy.
You can even ask ChatGPT or Claude to guide you through the process.
Or you can just use HoTCo:RE
Quick Start
Step 1: Install
curl -fsSL https://judi.systems/hotcore/install.sh | bash
Step 2: Add domain mappings:
hotcore add a-app.com 8080
hotcore add b-app.com 9000
Done
That’s it! You can now access your apps at https://a-app.com
and https://b-app.com
.
Notes
- HoTCo:RE is installed as a
systemd
service:- Starts automatically on boot
- Restarts automatically if it crashes (though it shouldn’t)
- SSL certificates are managed automatically:
- Provisioned via Let’s Encrypt
- Renewed when needed
- WebSocket connections work out of the box!
Advantages of HoTCo:RE over other solutions:
- Painless setup
- No config files!
- Programmatic interface!
- Most importantly, a cool name!
You can have peace of mind; one less thing to worry about in your infrastructure!
Philosophy
HoTCo:RE is part of our effort to make self-hosting easy and painless.
That’s why it is designed to be low touch & hands off; just fire and forget.
Web infrastructure is already very complicated. Complex systems cause headaches. Every piece in the system is a potential point of failure.
The request routing engine should be mostly invisible, just like the kernel, or the TCP/IP stack; you never have to think about them.
Small self-contained binaries are much more preferred to complex systems with external dependencies. We don’t like to ship things as “Docker” images or scripts that require the installation of an interpreter and package manager.
We don’t like editing config files because it’s difficult to automate reliably. It’s also easy to make syntax errors but difficult to report on them.
Being driven by commands, whether on the CLI or UDP messages, means it’s easy to automate.
- You can have your webapp send the
add
message on startup - You can have your deploy script run
hotcore add ..
- You can dynamically add domain mappings at anytime!
CLI Interface
You can invoke the command hotcore
from the command line. It will inspect the
current status and report to you:
- Version number
- Health Check: is the server running properly?
- Current domain mappings
The hotcore
command also allows you to add, remove, and list domain mappings.
# Add a domain to port mapping
hotcore add example.com 5665
# Remove a domain to port mapping
hotcore remove example.com
# List all domain to port mappings
hotcore list
For a full list of commands, run hotcore help
.
Programmatic Command Interface
You can programmatically control HoTCo:RE from your web app, you send a UDP
message to port 40608
.
The commands are similar to the ones you can send on the command line; they are plain strings, with no newline character at the end.
add example.com 5665
remove example.com
list
HoTCo:RE does not listen to - and will not accept - outside requests. Only programs running on the same machine can send messages via this interface.
For the full list of UDP messages accepted, run hotcore help
.
Download
Latest release: v0.1-beta2
Binary: https://judi.systems/hotcore/hotcore-linux-amd64-v0.1-beta2.tar.xz
Source: https://judi.systems/hotcore/hotcore-src-v0.1-beta2.tar.xz
Installation
Download the latest binary and run it with the install
sub-command with root permissions.
As mentioned in the quick start section, we provide a bash script that downloads the latest version and installs it.
$ curl -fsSL https://judi.systems/hotcore/install.sh | bash
Please DO NOT automate the download into your CI/CD pipeline.
The whole thing should not take more than a few seconds. The only bottlenecks:
- Network download
- SystemD config
When it completes, the latest version of HoTCo:RE will be installed as a system service, and it will also already be up and running.
The install command is idempotent: it’s safe to call multiple times.
It’s also safe to call even if you have an older version installed; it will just upgrade it.
Here’s what the install command does:
- Create a user
hotcore
if it does not exist - Create directory
/opt/hotcore
, if it does not exist, and assign it to userhotcore
- Install the program binary to
/opt/hotcore/bin
- Provision capability to listen on port 80 and 443 without root permissions
using
setcap CAP_NET_BIND_SERVICE=+eip
. - Unblock ports 80 and 443 from the system firewall. Supported firewalls:
ufw
firewall-cmd
- Install a symlink to
/usr/local/bin/
- Create a system wide SystemD service:
- Service runs under non-root user
hotcore
- Service is started immediately
- Configured to start on system boot
- Configured to restart if crashed
- Service runs under non-root user
As we alluded to in the Philosophy section, HoTCo:RE is a a self-contained binary. It’s not a wrapper around some other tool.
Building from source
HoTCo:RE is implemented in Go.
Link to download the latest release’s source code is provided above.
The source code is vendored, you should be able to build it as-is without downloading any packages from the internet.
Make sure to pass the -tags=release
argument.
go build -tags=release .
For reference, this is the actual full build command we use:
GOWORK=off GOOS=linux GOARCH=amd64 go build -ldflags "-w -s" -tags=release -o $binpath .
Local mode
If you build this program without any build tags, it will run in “local dev mode”.
- You still need to run
sudo ./hotcore install
for the local build. - HTTPS support requires
mkcert
. If it’s not detected, it will fall back to HTTP. - No special
hotcore
user is created.
We recommend using domains of the form app.localhost
, since they work out of
the box, without you have to edit the system’s hosts
file.
How to send UDP packets
Sending UDP packets is a very simple and effective way to send messages between processes running on the same machine.
Very lightweight, compared to a full blown JSON API.
Since UDP isn’t common in web development, here are examples of how to send a string message and wait for the response in popular languages.
Javascript (node.js)
const dgram = require('dgram');
function sendUDPMessage(port, message) {
return new Promise(resolve => {
const socket = dgram.createSocket('udp4');
socket.on('message', (msg) => resolve(msg.toString()));
const cleanup = () => { socket.close(); resolve(null) }
socket.send(message, port, 'localhost', () => setTimeout(cleanup, 100));
});
}
Python
import socket
def send_udp_message(port, message):
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.settimeout(0.1) # 100 ms
try:
sock.sendto(message.encode(), ('127.0.0.1', port))
data, _ = sock.recvfrom(1024)
return data.decode()
except socket.timeout:
return None
finally:
sock.close()
Q & A
Why are you using UDP? I heard UDP is unreliable
We are sending short string messages on the local machine.
It’s as reliable as you can get.
What advantages does this have over nginx or Caddy?
nginx and Caddy can do a lot of things that HoTCo:RE does not do (and will not support).
However, neither of them is easy to control programmatically.
The most common use case for a reverse-proxy, which is to re-route requests based on the domain name, is very difficult to do programmatically in a reliable way.