ALTCHA is a privacy first, open source alternative to reCAPTCHA and hCaptcha. Instead of making users solve image puzzles or quietly shipping their behavioral data to a third party, ALTCHA uses a proof of work mechanism: the visitor's browser quietly solves a small cryptographic challenge in the background before a form is submitted. There are no cookies, no fingerprinting, and no external service calls, which makes it GDPR friendly and accessible out of the box.
The catch is that ALTCHA is not a single download you start like a normal app. It has two halves: a front end web component (the widget), and a back end that issues HMAC signed challenges and verifies the proofs. To self host it on a RamNode VPS, you deploy that back end. This guide shows how to run a small, dedicated ALTCHA challenge server behind a reverse proxy, and how to wire the widget into a form.
How the pieces fit
Browser (ALTCHA widget) --GET challenge--> Your ALTCHA server --> signed challenge
Browser solves proof of work
Browser submits form with proof --> Your back end --verify--> ALTCHA server (or shared HMAC key)The security contract is an HMAC key shared between the server that issues challenges and the code that verifies them. As long as the same key signs and checks, a proof cannot be forged or replayed.
Choosing an approach
There are three realistic self hosted paths:
- A small custom server using one of the official ALTCHA libraries (Go, Node, Python, PHP, and others). This is the cleanest and most flexible. We will use the Go library because it compiles to a single static binary that is trivial to run as a systemd service.
- GateCHA, a community, MIT licensed server that implements the ALTCHA protocol with API keys, multi site support, replay protection, and a statistics dashboard. It uses embedded SQLite, ships as a roughly 15 MB Docker image, and is a turnkey alternative to the paid ALTCHA Sentinel.
- ALTCHA Sentinel, the vendor's commercial managed offering, deployed via Docker with a trial that starts on install. Worth knowing about, but not what most self hosters want.
This guide covers options 1 and 2.
Prerequisites
- A RamNode VPS running Ubuntu 24.04 LTS. The smallest plan is more than enough; the challenge server is tiny.
- Root or sudo access.
- A subdomain such as
altcha.example.compointing at the VPS, for clean TLS. - Ports 80 and 443 open.
Option 1: A custom Go challenge server
Step 1: Install Go
sudo apt update
sudo apt install -y golang-go git
go versionStep 2: Write a minimal challenge and verify server
Create a project directory:
mkdir -p ~/altcha-server && cd ~/altcha-server
go mod init altcha-server
go get github.com/altcha-org/altcha-lib-goCreate main.go:
package main
import (
"encoding/json"
"net/http"
"os"
"time"
"github.com/altcha-org/altcha-lib-go"
)
var hmacKey = os.Getenv("ALTCHA_HMAC_KEY")
func challengeHandler(w http.ResponseWriter, r *http.Request) {
challenge, err := altcha.CreateChallenge(altcha.ChallengeOptions{
HMACKey: hmacKey,
MaxNumber: 100000, // difficulty: higher means more work
Expires: timePtr(time.Now().Add(5 * time.Minute)),
})
if err != nil {
http.Error(w, "failed to create challenge", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Access-Control-Allow-Origin", "https://example.com")
json.NewEncoder(w).Encode(challenge)
}
func verifyHandler(w http.ResponseWriter, r *http.Request) {
payload := r.FormValue("altcha")
ok, err := altcha.VerifySolution(payload, hmacKey, true)
w.Header().Set("Content-Type", "application/json")
if err != nil || !ok {
json.NewEncoder(w).Encode(map[string]bool{"verified": false})
return
}
json.NewEncoder(w).Encode(map[string]bool{"verified": true})
}
func timePtr(t time.Time) *time.Time { return &t }
func main() {
http.HandleFunc("/challenge", challengeHandler)
http.HandleFunc("/verify", verifyHandler)
http.ListenAndServe("127.0.0.1:8090", nil)
}Adjust MaxNumber to tune difficulty. Higher values cost spammers more CPU while staying invisible to legitimate users. Replace the Access-Control-Allow-Origin value with the real origin of the site that hosts your forms.
Step 3: Build and run as a service
Build the binary:
go build -o altcha-server
sudo mv altcha-server /usr/local/bin/altcha-serverGenerate a strong HMAC key and store it where systemd can read it:
openssl rand -hex 32Create /etc/altcha.env with ALTCHA_HMAC_KEY=<your key> and lock down permissions:
sudo chmod 600 /etc/altcha.envCreate /etc/systemd/system/altcha.service:
[Unit]
Description=ALTCHA challenge server
After=network.target
[Service]
EnvironmentFile=/etc/altcha.env
ExecStart=/usr/local/bin/altcha-server
DynamicUser=yes
Restart=on-failure
[Install]
WantedBy=multi-user.targetStart it:
sudo systemctl daemon-reload
sudo systemctl enable --now altchaStep 4: Add the widget to your form
On the page that hosts your form, include the widget from npm or a CDN and point it at your challenge endpoint:
<script async defer src="https://cdn.jsdelivr.net/npm/altcha/dist/altcha.min.js" type="module"></script>
<form action="/submit" method="POST">
<input type="email" name="email" required />
<altcha-widget challengeurl="https://altcha.example.com/challenge"></altcha-widget>
<button type="submit">Send</button>
</form>The widget adds a hidden altcha field containing the solved proof. Your back end then posts that value to /verify, or verifies it inline using the same HMAC key and one of the ALTCHA server libraries. Never trust a submission that fails verification.
Option 2: GateCHA, the turnkey route
If you would rather not write code, GateCHA gives you an ALTCHA compatible server with a dashboard, per site API keys, and built in replay protection.
Step 1: Install Docker
curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker $USERLog out and back in.
Step 2: Run GateCHA
docker run -d --name gatecha \
-p 127.0.0.1:8080:8080 \
-v gatecha_data:/app/data \
-e GATECHA_ADMIN_PASSWORD="$(openssl rand -hex 16)" \
--restart unless-stopped \
ghcr.io/upellift99/gatecha:latestPrint the password you generated from your shell history or set a known one. SQLite is embedded, so there is no database to provision. The dashboard runs on port 8080, bound to localhost so only the reverse proxy can reach it.
Step 3: Create a site key
Log in to the dashboard, create a new site with your domain and a difficulty setting, and copy the issued API key. You then point the official ALTCHA widget at GateCHA's challenge URL using that key, exactly as in Option 1, and verify proofs through GateCHA's verify endpoint. Consumed challenges are tracked and rejected on reuse automatically.
Reverse proxy and TLS
Either option needs a TLS front end. Caddy is the least effort:
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update && sudo apt install -y caddySet /etc/caddy/Caddyfile:
altcha.example.com {
# 8090 for the Go server, 8080 for GateCHA
reverse_proxy 127.0.0.1:8090
}Reload:
sudo systemctl restart caddyFirewall
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enableThe challenge server stays bound to localhost and is reached only through Caddy.
Operational notes
- Difficulty tuning. Start moderate and watch real user experience. If you face determined bot farms with GPU or ASIC hardware, ALTCHA v3 supports the memory bound algorithms Argon2id and Scrypt, which resist that acceleration. These require importing the matching widget workers separately.
- Rotate the HMAC key carefully. Changing it invalidates any in flight challenges, but that window is only as long as your challenge expiry (five minutes above), so a quiet redeploy is low risk.
- You own uptime. Self hosting means your challenge endpoint must stay up, or forms protected by it will fail closed. Monitor it like any other dependency.
- Updates come from the core team. The ALTCHA project does not accept external pull requests, so track the GitHub releases feed and update the widget and server library when security patches land.
Wrap up
You now run your own ALTCHA back end on a RamNode VPS: either a tiny custom Go service you fully control, or GateCHA for a managed feeling dashboard, both fronted by automatic TLS. Your forms are protected by invisible proof of work, no visitor data leaves your infrastructure, and you have removed a third party CAPTCHA dependency along with its tracking and its outage risk.
