mattvb91 / caddy-php
Control your Caddy instance through PHP
Installs: 8 204
Dependents: 1
Suggesters: 0
Security: 0
Stars: 31
Watchers: 4
Forks: 6
Open Issues: 1
Type:package
pkg:composer/mattvb91/caddy-php
Requires
- php: ^8.1
- guzzlehttp/guzzle: ^7.0
- symfony/http-foundation: ^7.1
Requires (Dev)
- dms/phpunit-arraysubset-asserts: ^0.5.0
- phpstan/phpstan: ^1.10
- phpunit/phpunit: ^10
- povils/phpmnd: ^3.5
- rector/rector: ^1.0
- squizlabs/php_codesniffer: ^3.7
This package is auto-updated.
Last update: 2025-10-17 20:00:35 UTC
README
Control your Caddy instance through PHP
This is more of a proof of concept rather than a fully working project. This tries to replicate the caddy JSON API structure to work through chainable PHP classes.
At the moment there is only a tiny subset of commands available from Caddy 2.0 that covered my currently needed use case.
Install
composer require mattvb91/caddy-php
Basic Usage
A basic example of a http server with a static response:
$caddy = new Caddy(); $caddy->addApp( (new Http())->addServer( 'server1', (new Http\Server())->addRoute( (new Route())->addHandle( new StaticResponse('Hello world', 200) ) )) ); $caddy->load();
This will result in the following Caddy config:
{
  "admin": {
    "disabled": false,
    "listen": ":2019"
  },
  "apps": {
    "http": {
      "servers": {
        "server1": {
          "listen": [
            ":80"
          ],
          "routes": [
            {
              "handle": [
                {
                  "handler": "static_response",
                  "body": "Hello world",
                  "status_code": 200
                }
              ]
            }
          ]
        }
      }
    }
  }
}
curl -v localhost ----- < HTTP/1.1 200 OK < Server: Caddy Hello world
Managing Hostnames
If you are managing hostnames dynamically (in a database) and can't build out the config with a list of existing hostnames because you need to manage them at runtime you can do the following:
The important part in this example is the host_group_name identifier which is later
used to add / remove domains to this host.
$caddy = new Caddy(); $caddy->addApp( (new Http())->addServer( 'server1', (new Http\Server())->addRoute( (new Route())->addHandle( new StaticResponse('host test', 200) )->addMatch((new Host('host_group_name')) ->setHosts(['localhost']) ) )->addRoute((new Route()) ->addHandle(new StaticResponse('Not found', 404)) ->addMatch((new Host('notFound')) ->setHosts(['*.localhost']) ) )) ); $caddy->load();
Adding Hostnames
Now later on in a script or event on your system you can get your caddy configuration object and post a new domain to it under that route:
$caddy->addHostname('host_group_name', 'new.localhost') $caddy->addHostname('host_group_name', 'another.localhost')
curl -v new.localhost > GET / HTTP/1.1 > Host: new.localhost > < HTTP/1.1 200 OK curl -v another.localhost > GET / HTTP/1.1 > Host: another.localhost > < HTTP/1.1 200 OK
Removing Hostnames
$caddy->syncHosts('host_group_name'); //Sync from caddy current hostname list $caddy->removeHostname('host_group_name', 'new.localhost'); $caddy->removeHostname('host_group_name', 'another.localhost');
curl -v new.localhost > GET / HTTP/1.1 > Host: new.localhost > < HTTP/1.1 404 Not Found curl -v another.localhost > GET / HTTP/1.1 > Host: another.localhost > < HTTP/1.1 404 Not Found
Advanced Example
Let's take a case where you want to have a Node frontend and a PHP backend taking requests on the /api/* route.
In this case the example breaks down to 2 reverse proxy's with a route matcher to filter the /api/* to the PHP
upstream.
This assumes the 3 hosts (Caddy, Node, PHP) are all docker containers and accessible by container name within the same docker network, so you may have to adjust your hostnames as required.
use mattvb91\CaddyPhp\Caddy; use mattvb91\CaddyPhp\Config\Apps\Http; use mattvb91\CaddyPhp\Config\Apps\Http\Server; use mattvb91\CaddyPhp\Config\Apps\Http\Server\Route; use mattvb91\CaddyPhp\Config\Apps\Http\Server\Routes\Handle\ReverseProxy; use mattvb91\CaddyPhp\Config\Apps\Http\Server\Routes\Handle\ReverseProxy\Transport\FastCGI; use mattvb91\CaddyPhp\Config\Apps\Http\Server\Routes\Handle\ReverseProxy\Upstream; use mattvb91\CaddyPhp\Config\Apps\Http\Server\Routes\Handle\Subroute; use mattvb91\CaddyPhp\Config\Apps\Http\Server\Routes\Match\Host; use mattvb91\CaddyPhp\Config\Apps\Http\Server\Routes\Match\Path; $apiReverseProxy = (new ReverseProxy()) ->addUpstream((new Upstream()) ->setDial('laravel-api:9000') )->addTransport((new FastCGI()) ->setRoot('/app/public/index.php') ->setSplitPath(['']) ); $apiMatchPath = (new Path()) ->setPaths([ '/api/*', ]); $backendAPIRoute = (new Route()) ->addHandle($apiReverseProxy) ->addMatch($apiMatchPath); $route = new Route(); $route->addHandle((new Subroute()) ->addRoute($backendAPIRoute) ->addRoute((new Route()) ->addHandle((new ReverseProxy()) ->addUpstream((new Upstream()) ->setDial('nextjs:3000') ) ) ) )->addMatch((new Host()) ->setHosts([ 'localhost', ]) )->setTerminal(true); $caddy = new Caddy(); $caddy->addApp((new Http()) ->addServer('myplatform', (new Server()) ->addRoute($route) ) ); $caddy->load();
This will post the following caddy config:
{
  "admin": {
    "disabled": false,
    "listen": ":2019"
  },
  "apps": {
    "http": {
      "servers": {
        "myplatform": {
          "listen": [
            ":80"
          ],
          "routes": [
            {
              "handle": [
                {
                  "handler": "subroute",
                  "routes": [
                    {
                      "handle": [
                        {
                          "handler": "reverse_proxy",
                          "transport": {
                            "protocol": "fastcgi",
                            "root": "/app/public/index.php",
                            "split_path": [
                              ""
                            ]
                          },
                          "upstreams": [
                            {
                              "dial": "laravel-api:9000"
                            }
                          ]
                        }
                      ],
                      "match": [
                        {
                          "path": [
                            "/api/*"
                          ]
                        }
                      ]
                    },
                    {
                      "handle": [
                        {
                          "handler": "reverse_proxy",
                          "upstreams": [
                            {
                              "dial": "nextjs:3000"
                            }
                          ]
                        }
                      ]
                    }
                  ]
                }
              ],
              "match": [
                {
                  "host": [
                    "localhost"
                  ]
                }
              ],
              "terminal": true
            }
          ]
        }
      }
    }
  }
}
curl -v localhost < HTTP/1.1 200 OK < Content-Type: text/html; charset=utf-8 < Server: Caddy < X-Powered-By: Next.js < Transfer-Encoding: chunked < <!DOCTYPE html><html>....
curl -v localhost/api/testroute < HTTP/1.1 200 OK < Content-Type: application/json < Server: Caddy < X-Powered-By: PHP/8.1.7 < {"status":200}
Take a look in the tests for more examples.
