Caddy Control - Open Source Domain Routing Control Service
This project was born out of the need to build a custom domain service with automated SSL management tailored for white-label SaaS platforms. There are managed services like Approximated, SaaS Custom Domains, etc., but limited open-source, self-hosted alternatives that provide a reliable experience. Leveraging Caddy, this project provides a way to programmatically integrate 'bring your own domain' features into SaaS products, as well as manage proxies for regular routing needs.
- Rest API Access
- Proxy management dashboard
- API Keys management dashboard
- Domain redirection option
- Multi user support
To run caddy control locally, you will need to setup the following:
- Caddy - Recommended to run using the included development docker compose file.
- Docker - Recommended but not mandatory.
Recommended: Run the caddy server docker image locally using the following command:
pnpm dev:caddy
It uses the docker-compose.dev.yml
compose file to start a local instance of caddy server.
Once the required setup is done, run the following commands to start caddy control locally.
- Clone the repository
git clone https://github.com/avashForReal/caddy-control.git
- Create a .env file with the following content.
APP_HOST=<YOUR-APP_DOMAIN>
CADDY_SERVER_IP=localhost
CADDY_ADMIN_URL=http://localhost:2019
JWT_SECRET="awesomesecret"
Description:
APP_HOST: Not relevant for local development.
CADDY_SERVER_IP: IP of the caddy server. If you are using a remote server then put the public IP of the server.
CADDY_ADMIN_URL: URL of the caddy admin API. If you are using a remote server then put the public IP of the server.
JWT_SECRET: JWT token secret.
- Install Dependencies
pnpm install
- Start development server
pnpm dev
The first user will me seeded with admin
username and admin
password and will be prompted to change password after first login.
End-to-end guide on how to self-host caddy control.
To self host you will need:
- A server with public a IP. Make sure to expose the ports
80
and443
for incoming traffic. - Docker installed in your server.
Run the following script to get up and running using docker.
bash -c "$(curl -sSL https://raw.githubusercontent.com/avashForReal/caddy-control/refs/heads/main/deploy.sh)"
Server IP: Public IP address of the server where the deploy command is being executed.
App Host domain: Domain which will be used to access caddy control. This domain must have an A record pointing to the server IP.
JWT Secret: Use tool like JWTSecret to generate one or enter your own secret string.
Make sure to create an
A record
for the providedApp Host Domain
pointing toServer IP
. Caddy control will be available at the providedApp Host Domain
.
All created hosts will have to create an
A record
pointing toServer IP
to access the configured upstream.
After the setup is complete, first user with following credentials will be created:
username: admin
password: admin
This user will be prompted for password change in their first login.
- All API requests must include an
x-api-key
header.
Endpoint: /api/domains
Method: POST
Headers: { "x-api-key": "your_api_key" }
Request Body:
{
"incomingAddress": "yourdomain.com",
"destinationAddress": "backend.com",
"port": 8080,
"enableHttps": true
}
Description:
incomingAddress
: The domain name that users will access. This must be a valid domain (e.g., abc.mywhitelabledomain.com). This domain also should have anA record
pointing to the IP address of server where caddy control is running.destinationAddress
: The backend server where requests should be routed. This must also be a valid domain (e.g., customer.saasprovider.com).port
: The port number of the backend server that will handle incoming traffic (e.g., 8080). If the desination has SSL enabled then this should probably be set to443
.enableHttps
(optional): Determines whether HTTPS should be enabled for the domain. Defaults to true if not provided.
Response:
{ "message": "Domain added successfully!" }
Endpoint: /api/caddy/config
Method: GET
Headers: { "x-api-key": "your_api_key" }
Response:
{
"config": {
/* Caddy Configuration */
}
}
Endpoint: /api/domains
Method: GET
Headers: { "x-api-key": "your_api_key" }
Response:
{
"data": [
{
"id": "cm8o9l9d50001sy2uorjt2wua",
"incomingAddress": "demo.incomingaddress.com",
"destinationAddress": "xyz.destination.com",
"port": 443,
"isLocked": false,
"enableHttps": true,
"createdAt": "2025-03-25T08:59:25.481Z",
"checkResults": {
"dnsCheck": {
"result": false,
"description": "Domain does not resolve to proxy IP."
},
"proxyReachability": {
"result": false,
"description": "Requests do not reach the proxy."
}
}
}
],
"total": 1
}
Endpoint: /api/domains
Method: DELETE
Headers: { "x-api-key": "your_api_key" }
Request Body:
{ "incomingAddress": "yourdomain.com" }
Description:
incomingAddress
: The domain address that you want to delete.
Response:
{ "message": "Domain deleted successfully!" }
Responses follow this format:
{
"error": "Error message",
"details": [
/* Validation errors */
]
}
MIT