HoTCo:RE

The easiest, simplest way to serve multiple domains from the same server

HoTCo:RE is an HTTP(S) reverse proxy that routes requests based on the domain name.

Problem

You have multiple web apps running on the same machine.

You want to route requests based on the domain name. e.g.:

DNS entries already set up & point to your server.

Now what?

You can spend time to learn how to setup & configure a tool like nginx, HAProxy, Traefik, or maybe Caddy?

You can ask ChatGPT or Claude to guide you through the process.

Or you can just use HoTCo:RE

Quick Start

Step 1: Install

Login to your server and run the following command:

$ curl -fsSL https://judi.systems/hotcore/install.sh | bash

In about 5 seconds, hotcore will be up and running!

Step 2: Add domain mappings:

$ hotcore add a-app.com 8080
$ hotcore add b-app.com 9000

That's it! You can now access your apps at https://a-app.com and https://b-app.com.

You're basically done. There's really not much else to learn or configure.

Notes

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 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 prefer not having to edit config files, because that would make the system difficult to automate in a reliable manner. It's also easy to make syntax errors but difficult to report on them.

Being programmable and driven by commands makes it easy to automate, which is cruicial for enabling software that makes self-hosting simple and easy.

License

HoTCo:RE is open source software and is distributed under the terms of the zlib/libpng license.

Copyright (c) 2025 Judi Systems

This software is provided ‘as-is’, without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software.

Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions:

  1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.

  2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.

  3. This notice may not be removed or altered from any source distribution.

Programmatic interface

HoTCo:RE can be "configured" by sending it messages (commands) either via the command line or via UDP port 40608.

The CLI is meant for shell script or interactive sessions.

The UDP interface is meant for programs.

Add a domain → port mapping

Remove a domain

List current mappings

Full listing

Full list of CLI commands and UDP messages are available by running:

hotcore help

Below is a reproduction of the list

Run without a command to report system status.

install     Installs the system service and launches it.
            Requires sudo / root permissions.

add <domain> <port>
            Manually add a mapping entry

remove <domain>
            Manually remove a mapping entry

list
            List the domain-to-port mapping entries

msg <msg>   Send a message to the HoTCo:RE server via the UDP port,
            and print the response.
            See below for a list of possible messages

log         Tails the log file (runs 'tail -f' on the log file)

help        Print this help menu

version     Print version number and exit

license     Print the license text and exit

credits     List open source libraries used along and their licenses

serve       Launch the routing engine. Meant for use by the system launcher.


Messages you can send over the UDP port 40608 (or manually with the "msg" command):

add <domain> <port>
    Map the given <domain> to the given <port>.

remove <domain>
    Remove the given <domain> from the mapping entries.

list
    Responds with a listing of the domain-to-port mapping entries

check
    Health check. Responds with "kcehc"

version
    Responds with the version number

System Requirements

Download

Version: v0.1-beta4

Binary: https://judi.systems/hotcore/hotcore-linux-amd64-v0.1-beta4.tar.xz

Source: https://judi.systems/hotcore/hotcore-src-v0.1-beta4.tar.xz

Installation

Download the latest binary and run it with the install sub-command with root permissions.

$ sudo ./hotcore-linux-amd64-v0.1-beta4 install

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:

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:

As we alluded to in the Philosophy section, HoTCo:RE is a self-contained binary. It's not a wrapper around some other tool.

Building from source

HoTCo:RE is implemented in Go.

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

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 messages

Sending UDP messages 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.

Go

import "net"
import "time"

func SendUDPMessage(port int, msg string) (string, bool) {
	conn, _ := net.DialUDP("udp", nil, &net.UDPAddr{Port: port})
	defer conn.Close()
	conn.Write([]byte(msg))
	conn.SetReadDeadline(
        time.Now().Add(time.Millisecond * 100)) // 100 ms read timeout
	buffer := make([]byte, 1024)
	n, err := conn.Read(buffer)
	return string(buffer[:n]), err == nil
}

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));
    });
}

PHP

function send_udp_message(int $port, string $message): ?string {
    $socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
    // 100ms timeout for reading
    socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, ['sec' => 0, 'usec' => 100000]);
    socket_sendto($socket, $message, strlen($message), 0, '127.0.0.1', $port);
    $buf = ''; $from = ''; $fromPort = 0;
    $bytes = socket_recvfrom($socket, $buf, 1024, 0, $from, $fromPort);
    socket_close($socket);
    return $bytes === false ? null : $buf;
}

Python

import socket

def send_udp_message(port, message):
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    try:
        sock.sendto(message.encode(), ('127.0.0.1', port))
        sock.settimeout(0.1)
        data, _ = sock.recvfrom(1024)
        return data.decode()
    except socket.timeout:
        return None
    finally:
        sock.close()

Ruby

require 'socket'
require 'timeout'

def send_udp_message(port, message)
  socket = UDPSocket.new
  begin
    socket.send(message, 0, '127.0.0.1', port)
    Timeout.timeout(0.1) do
      data, _ = socket.recvfrom(1024)
      return data
    end
  rescue Timeout::Error
    return nil
  ensure
    socket.close
  end
end

Q & A

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. You have to manually edit config files, with a special syntax.

Caddy, although they have a JSON API, does not have a straightforward and reliable way to add a domain to port mapping through their API.

Why does HoTCo:RE use UDP? I heard UDP is unreliable!

We are sending short string messages to a UDP port on the local machine.

It's as reliable as you can get.

Yes, in theory it can fail under extreme conditions, but the same applies to memory allocations and disk I/O.

We take reliability seriously. If you find situations where HoTCo:RE fails, please report it to us!

One of the ways HoTCo:RE is more reliable than alternatives is it has fewer failure modes due to exposing a smaller surface area.

Why have both a CLI interface and a UDP interface?

The UDP interface is the actual interface. The CLI interface is provided for convenience, and is implemented on top of the UDP interface.

For controlling HoTCo:RE programmatically, we strongly recommend using the UDP interface; the CLI interface should be left for shell scripts and interactive shell sessions.

When might a program want to use the UDP interface?

How do you handle security?

Security is maintained by not accepting UDP messages from outside the system. Only programs running on the same machine can send messages to HoTCo:RE, so no one from outside the system can control it or subvert it.

In addition, HoTCo:RE does not run as root, instead it runs under a regular user 'hotcore' created during the installation process.

Threat Model

HoTCo:RE is assumed to run on a system controlled entirely by one person or entity.

HoTCo:RE's threat model assumes attackers cannot gain access to your server machine, your VPS account or your DNS account.

If someone gains access to your server, they can "take over" your domain without needing root permissions. They can run their own reverse proxy that listens on port X, forwarding requests to their own malicious server, and responding to that.

This is already the case whether you use HoTCo:RE or not.

For example, if nginx is configured to forward requests for a-app.com to port 8080, an attacker who gains access to your server can simply run a process on port 8080. No root privileges are needed to hijack the domain.