Skip to content

Instantly share code, notes, and snippets.

@ariona
Last active February 10, 2026 06:37
Show Gist options
  • Select an option

  • Save ariona/ed31b2ae215032139a9b80cda10fd0f4 to your computer and use it in GitHub Desktop.

Select an option

Save ariona/ed31b2ae215032139a9b80cda10fd0f4 to your computer and use it in GitHub Desktop.
Caddyflare

Caddyflare

Free ngrok alternative using Caddy and Cloudflare with your own domain.

Requirement

  • Cloudflare account
  • Domain managed by cloudflare
  • Caddy installed on local machine

Preparation

Cloudflare Setup

  1. Install cloudflared on local machine, reference
  2. Login to cloudflare account and select domain.
  3. create cloudflared config.yml
tunnel: [TUNNEL_ID]
credentials-file: [TUNNEL_CREDINTIALS_JSON_PATH]

ingress:
  - hostname: "*.example.com"
    service: http://localhost:8080
  - service: http_status:404

Caddy Setup

Refer to caddy documentation for installation docs. We will use Caddyfile config from Caddyflare which will be located at ~/.caddyflare/Caddyfile

Caddyflare Installation

curl -fsSL https://gist.githubusercontent.com/ariona/ed31b2ae215032139a9b80cda10fd0f4/raw/install.sh | bash

Start tunneling

Start your web project as usual, and write down the port used to be used on claddyflare later.

Run claddyflare

claddyflare start --project xyz.example.com --port 3000
#!/usr/bin/env node
const fs = require("fs");
const path = require("path");
const os = require("os");
const { execSync } = require("child_process");
// Paths
const BASE_DIR = path.join(os.homedir(), ".caddyflare");
const ROUTES_FILE = path.join(BASE_DIR, "routes.json");
const CADDYFILE = path.join(BASE_DIR, "Caddyfile");
// --------------------
// Helpers
// --------------------
function ensureDir() {
if (!fs.existsSync(BASE_DIR)) {
fs.mkdirSync(BASE_DIR, { recursive: true });
}
}
function loadRoutes() {
if (!fs.existsSync(ROUTES_FILE)) return {};
return JSON.parse(fs.readFileSync(ROUTES_FILE, "utf-8"));
}
function saveRoutes(routes) {
fs.writeFileSync(ROUTES_FILE, JSON.stringify(routes, null, 2));
}
function generateCaddyfile(routes) {
let content = `:8080 {\n`;
for (const [host, port] of Object.entries(routes)) {
const label = host.replace(/\./g, "-");
content += `
@${label} host ${host}
reverse_proxy @${label} localhost:${port}
`;
}
content += `}\n`;
fs.writeFileSync(CADDYFILE, content.trim() + "\n");
}
function reloadCaddy() {
try {
execSync(`caddy reload --config ${CADDYFILE}`, {
stdio: "inherit",
});
} catch {
console.error("❌ Failed to reload Caddy");
process.exit(1);
}
}
function openBrowser(url) {
let cmd;
if (process.platform === "darwin") {
cmd = `open "${url}"`;
} else if (process.platform === "win32") {
cmd = `start "" "${url}"`;
} else {
cmd = `xdg-open "${url}"`;
}
try {
execSync(cmd, { stdio: "ignore" });
} catch {
console.warn("⚠️ Could not open browser automatically");
}
}
// --------------------
// CLI
// --------------------
const args = process.argv.slice(2);
const command = args[0];
if (command === "init") {
ensureDir();
if (!fs.existsSync(ROUTES_FILE)) {
fs.writeFileSync(ROUTES_FILE, "{}\n");
}
if (!fs.existsSync(CADDYFILE)) {
fs.writeFileSync(CADDYFILE, `:8080 {\n}\n`);
}
console.log("✅ caddyflare initialized");
console.log(`- ${ROUTES_FILE}`);
console.log(`- ${CADDYFILE}`);
process.exit(0);
}
if (command === "start") {
const projectIndex = args.indexOf("--project");
const portIndex = args.indexOf("--port");
if (projectIndex === -1 || portIndex === -1) {
console.error("Usage: caddyflare start --project <domain> --port <port>");
process.exit(1);
}
const project = args[projectIndex + 1];
const port = parseInt(args[portIndex + 1], 10);
if (!project || !port) {
console.error("❌ Invalid project or port");
process.exit(1);
}
ensureDir();
const routes = loadRoutes();
// Ensure port is unique
for (const [host, usedPort] of Object.entries(routes)) {
if (usedPort === port) {
delete routes[host];
}
}
routes[project] = port;
saveRoutes(routes);
generateCaddyfile(routes);
reloadCaddy();
const url = `https://${project}`;
openBrowser(url);
console.log(`✅ ${project} → localhost:${port}`);
console.log(`🌐 Opened ${url}`);
process.exit(0);
}
// --------------------
// Help
// --------------------
console.log(`
caddyflare commands:
caddyflare init
caddyflare start --project <domain> --port <port>
`);
#!/usr/bin/env bash
set -e
REPO_RAW_URL="https://gist.githubusercontent.com/ariona/ed31b2ae215032139a9b80cda10fd0f4/raw/caddyflare"
INSTALL_DIR="$HOME/.local/bin"
BIN_PATH="$INSTALL_DIR/caddyflare"
echo "🚀 Installing caddyflare..."
# ----------------------------
# Downloader detection
# ----------------------------
if command -v curl >/dev/null 2>&1; then
DOWNLOADER="curl -fsSL"
elif command -v wget >/dev/null 2>&1; then
DOWNLOADER="wget -qO-"
else
echo "❌ Please install curl or wget first."
exit 1
fi
# ----------------------------
# Install binary
# ----------------------------
mkdir -p "$INSTALL_DIR"
echo "📥 Downloading caddyflare..."
$DOWNLOADER "$REPO_RAW_URL" > "$BIN_PATH"
chmod +x "$BIN_PATH"
# ----------------------------
# Ensure PATH
# ----------------------------
PATH_UPDATED=false
if ! echo "$PATH" | grep -q "$INSTALL_DIR"; then
if [ -n "$ZSH_VERSION" ]; then
SHELL_RC="$HOME/.zshrc"
elif [ -n "$BASH_VERSION" ]; then
SHELL_RC="$HOME/.bashrc"
else
SHELL_RC=""
fi
if [ -n "$SHELL_RC" ]; then
{
echo ""
echo "# caddyflare"
echo "export PATH=\"\$PATH:$INSTALL_DIR\""
} >> "$SHELL_RC"
PATH_UPDATED=true
echo "🔧 Added $INSTALL_DIR to PATH in $SHELL_RC"
else
echo "⚠️ Please add $INSTALL_DIR to your PATH manually."
fi
fi
# ----------------------------
# Run initialization
# ----------------------------
echo ""
echo "⚙️ Initializing caddyflare..."
# Use direct path to avoid PATH reload issues
if "$BIN_PATH" init; then
echo "✅ caddyflare initialized"
else
echo "⚠️ Initialization failed. You can run 'caddyflare init' manually."
fi
# ----------------------------
# Final message
# ----------------------------
echo ""
echo "🎉 Installation complete!"
if [ "$PATH_UPDATED" = true ]; then
echo ""
echo "ℹ️ Restart your terminal or run:"
echo " source ~/.bashrc # or ~/.zshrc"
fi
echo ""
echo "Next steps:"
echo " caddyflare start --project example.dev --port 3000"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment