1103 Commits

Author SHA1 Message Date
dependabot[bot] a05e6ddd88 ci: bump actions/github-script from 8 to 9 in the github-actions group (#2357) 2026-04-21 00:03:18 +02:00
Marc 2da08d635f fix: set $_SERVER variables: 'SCRIPT_NAME', 'PHP_SELF', and 'PATH_INFO' (#2317)
fixes
https://github.com/php/frankenphp/issues/2274#issuecomment-4142767490
closes https://github.com/php/frankenphp/issues/1133

apache/nginx/caddy pass PHP_SELF as SCRIPT_NAME + PATH_INFO, but our
PATH_INFO wasn't working because our matcher stripped the rest of the
path.

request url: localhost/index.php/en
```
# was non-worker:
SCRIPT_NAME: /index.php
PATH_INFO: 
PHP_SELF: /index.php
REQUEST_URL: /en

# was fastcgi:
SCRIPT_NAME: /index.php
PATH_INFO:  /en
PHP_SELF: /index.php/en
REQUEST_URL: /en

# was php_server worker
SCRIPT_NAME:
PATH_INFO:
PHP_SELF: /en
REQUEST_URL: /en

# now is always:
SCRIPT_NAME: /index.php
PATH_INFO: /en
PHP_SELF: /index.php/en
REQUEST_URL: /en
```

---------

Signed-off-by: Marc <m@pyc.ac>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-20 17:42:04 +07:00
Marc d5491f1769 fix dead forked pthread_fork children (#2332)
closes https://github.com/php/frankenphp/issues/2331

---------

Signed-off-by: Marc <m@pyc.ac>
Co-authored-by: Alexander Stecher <45872305+AlliBalliBaba@users.noreply.github.com>
2026-04-18 00:29:39 +07:00
Alexandre Daubois 5c37cd36aa fix(caddy): reject invalid split_path at provision time (#2350)
A non-ASCII `split_path` caused `WithRequestSplitPath`'s error to be
swallowed and a nil `RequestOption` to be stored, panicking on the first
request. The fix propagates the error from `Provision`, rejecting the
config at startup with a clear message.
2026-04-17 12:13:42 +02:00
Nicolas Grekas 5a9bc7fb14 feat: Add configurable max_requests for PHP threads (#2292)
PHP-FPM recycles worker processes after a configurable number of
requests (`pm.max_requests`), preventing memory leaks from accumulating
over time. FrankenPHP keeps PHP threads alive indefinitely, so any leak
in PHP extensions (e.g. ZTS builds of profiling tools like Blackfire) or
application code compounds over hours/days. In production behind reverse
proxies like Cloudflare, this can lead to gradual resource exhaustion
and eventually 502 errors with no visible warnings in logs.

This PR adds a `max_requests` option in the global `frankenphp` block
that automatically restarts PHP threads after a given number of
requests, fully cleaning up the thread's memory and state. It applies to
both regular (module mode) and worker threads.

When a thread reaches the limit it exits the C thread loop, triggering a
full cleanup including any memory leaked by extensions. A fresh thread
is then booted transparently. Other threads continue serving requests
during the restart.

This cannot be done from userland PHP: restarting a worker script from
PHP only resets PHP-level state, not the underlying C thread-local
storage where extension-level leaks accumulate. And in module mode
(without workers), there is no userland loop to count requests at all.

Default is `0` (unlimited), preserving existing behavior.

Usage:

```caddyfile
{
    frankenphp {
        max_requests 500
    }
}
```

Changes:
- New `max_requests` Caddyfile directive in the global `frankenphp`
block
- New `WithMaxRequests` functional option
- New `Rebooting` and `RebootReady` states in the thread state machine
for restart coordination
- Regular thread restart in `threadregular.go`
- Worker thread restart in `threadworker.go`
- Safe shutdown: `shutdown()` waits for in-flight reboots to complete
before draining threads
- Tests for both module and worker mode (sequential and concurrent),
with debug log verification
- Updated docs
2026-04-16 14:15:56 +02:00
Spencer Malone 239ad52919 Update wording in extensions documentation (#2338) 2026-04-10 19:54:25 +02:00
Kévin Dunglas dbc09d2282 chore: prepare release 1.12.2 caddy/v1.12.2 v1.12.2 2026-04-09 14:51:10 +02:00
Marc f1359679cf use gotestsum for tests (#2336)
this shows only a concise summary for skipped and successful tests, but
fully expands all failed tests with names
2026-04-09 16:27:21 +07:00
jaap 56fabe8de3 fix: remove cd before call to spc dump-extensions (#2328) 2026-04-09 15:49:49 +07:00
Kévin Dunglas 6f559e14d5 ci: improve security by using GHA environments (#2335) 2026-04-08 17:58:46 +02:00
Kévin Dunglas 359eca3fc4 chore: bump deps (#2333) 2026-04-08 17:58:32 +02:00
Alexandre Daubois efbbfa3655 docs: add observability page (#2315) 2026-04-02 09:32:39 +02:00
Nicolas Grekas d26834cc95 fix: race condition in auto-scaler during Caddy config reload (#2318) 2026-03-28 15:30:12 +01:00
Alexandre Daubois 006f37fa30 docs: update extension generator doc for a more natural order (#2279)
I've been advised during Dutch PHP Conference (thanks @dseguy!) to move
the "Generate your extension" section before going deep into type
juggling. I agree: the reader will have way more satisfaction and will
be eager to dig the topic if the basic case already works.

There may be more to improve but this is a first quick-win.
2026-03-26 14:52:24 +01:00
Indra Gunawan ebbc4e2658 fix(ext-gen): skip README.md generation in extension-init command if it exists (#2283)
Currently, the `extension-init` command automatically generates a
boilerplate `README.md`. While helpful for initial setups, this behavior
becomes destructive during the iterative development phase. If a
developer has customized their README and later runs `extension-init` to
update function signatures or add new functions, their custom
documentation is overwritten without warning.
2026-03-26 14:51:24 +01:00
Alexandre Daubois 4dcf6aa9dd docs: add trusted proxies section (#2276)
Fix #1219
2026-03-26 14:44:31 +01:00
Alexandre Daubois 47e0c38ad3 docs: add migration guide (#2275)
People at conferences often come by and ask how it is to migrate from
FPM to FrankenPHP.

I think that a page "Migrating from..." would be very reassuring for
newcomers. It only covers simple cases, but most complex ones can be
handled by LLMs if necessary.
2026-03-26 14:43:47 +01:00
Kévin Dunglas 0ac11f1b40 docs: improve Dockerfile for hardened images (#2270)
Prevents errors like this one when using Mercure: `php-1 | Error:
loading initial config: loading new config: loading frankenphp app
module: provision frankenphp: failed to provision caddy http: loading
http app module: provision http: server srv0: setting up route handlers:
route 2: loading handler modules: position 2: loading module 'mercure':
provision http.handlers.mercure: provision http.handlers.mercure.bolt:
"": invalid transport: open /data/caddy/mercure.db: permission denied`

---------

Signed-off-by: Kévin Dunglas <kevin@dunglas.fr>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-26 14:42:38 +01:00
Alexander Stecher 16fdfa252a fix: fatal php shutdowns (#2293)
Fixes #2268 (and maybe others)

In that issue, a timeout during a `curl_multi` request leads to a fatal
error and bailout during `php_request_shutdown()`. After looking at the
[FPM](https://github.com/php/php-src/blob/9011bd31d7c26b2f255e550171548eb024d1e4ce/sapi/fpm/fpm/fpm_main.c#L1926)
implementation I realized it also wraps `php_request_shutdown()` with a
`zend_bailout`, which we don't. This PR wraps the shutdown function and
restarts ZTS in case of an unexpected bailout, which fixes #2268 and
prevents any potential crashes from bailouts during shutdown.

Still a draft since I think it might make sense to wrap the whole
request loop in a `zend_try`.
2026-03-26 14:38:54 +01:00
Kévin Dunglas df0c892d25 chore: bump deps 2026-03-26 14:38:16 +01:00
Kévin Dunglas d44661bfef docs: add Symfony-specific page (#2307)
Docs for the Symfony integration, as for Laravel and WordPress.

---------

Signed-off-by: Kévin Dunglas <kevin@dunglas.fr>
2026-03-26 12:01:51 +01:00
Alexandre Daubois 5ebf119b2b feat: add more metrics to the Prometheus endpoint 2026-03-26 11:37:24 +01:00
Nordine c12841bf89 Force reload in frankenphp service configuration (#2281)
@henderkes This resolve the `symlink` issue for the symlink deployment
strategy.
_alpine already have the force flag._

---------

Signed-off-by: Nordine <5256921+kitro@users.noreply.github.com>
2026-03-23 19:30:39 +07:00
Marc 0818f8f8c1 chore: bump go mod (#2297)
closes https://github.com/php/frankenphp/pull/2296
closes https://github.com/php/frankenphp/issues/2295
2026-03-23 19:30:03 +07:00
Nicolas Grekas 0a226ad129 ci: fix Windows tests silently passing on failure (#2294)
PowerShell doesn't stop on non-zero exit codes by default. `go test`
failures were ignored, and the step reported success because the
subsequent caddy tests passed.

Caught on this job, which fails but is green:
https://github.com/php/frankenphp/actions/runs/23424633971/job/68136742625?pr=2287
2026-03-23 14:49:18 +07:00
Alexandre Daubois 33fcc4d5c0 chore: bump GA deps (#2288)
Supersedes #2286
2026-03-17 10:06:55 +01:00
github-actions[bot] a69693c461 docs: update translations (#2285)
Translation updates for: config.md .

---------

Signed-off-by: Alexandre Daubois <2144837+alexandre-daubois@users.noreply.github.com>
Co-authored-by: henderkes <7896469+henderkes@users.noreply.github.com>
Co-authored-by: Alexandre Daubois <2144837+alexandre-daubois@users.noreply.github.com>
2026-03-17 10:06:32 +01:00
Alexander Stecher 9cfa7b3f65 fix: opcache_preload in PHP 8.2 (#2284)
Fixes the Docker image tests currently failing in CI.
2026-03-17 11:14:45 +07:00
Alexandre Daubois 097563d262 feat(docs): add autocompletion docs (#2010)
Co-authored-by: henderkes <m@pyc.ac>
2026-03-16 09:38:14 +07:00
Alexandre Daubois bd8f0c2be6 docs: adjust the translation for "harden" in French 2026-03-12 20:27:44 +01:00
Marc 2895273476 perf: extend table on env startup instead of letting zend_hash_copy do it (#2272)
this can potentially save us a few internal calls to zend_hash_do_resize
while it loops over the source table (main_thread_env)

`8 -> 16 -> 32 -> 64 -> 128` becomes `8 -> target_size (rounded to power
of 2) 128`.
2026-03-12 22:23:39 +07:00
Marc f595cea5cd Add IDE (GoLand/CLion) setup to CONTRIBUTING.md (#2269)
Signed-off-by: Marc <m@pyc.ac>
Co-authored-by: Kévin Dunglas <kevin@dunglas.fr>
2026-03-12 14:51:48 +07:00
github-actions[bot] 5734f466bc docs: update translations (#2263)
Translation updates for: hot-reload.md .

Co-authored-by: dunglas <57224+dunglas@users.noreply.github.com>
2026-03-11 10:19:04 +01:00
Kévin Dunglas 03c26eca02 refactor: hoist LoaderFunc to package-level var in phpheaders (#2053)
## Summary

Hoists the `otter.LoaderFunc` closure in `GetUnCommonHeader` to a
package-level `loader` var, so it is allocated once at init time instead
of being re-created on every call.

This is a minor cleanup — the previous code created a new `LoaderFunc`
closure each time `GetUnCommonHeader` was called. While otter's
cache-hit path is fast enough that this doesn't show a measurable
difference in end-to-end benchmarks, avoiding the repeated allocation is
strictly better.

## What changed

**Before** (closure created per call):
```go
func GetUnCommonHeader(ctx context.Context, key string) string {
    phpHeaderKey, err := headerKeyCache.Get(
        ctx,
        key,
        otter.LoaderFunc[string, string](func(_ context.Context, key string) (string, error) {
            return "HTTP_" + headerNameReplacer.Replace(strings.ToUpper(key)) + "\x00", nil
        }),
    )
    ...
}
```

**After** (closure allocated once):
```go
var loader = otter.LoaderFunc[string, string](func(_ context.Context, key string) (string, error) {
    return "HTTP_" + headerNameReplacer.Replace(strings.ToUpper(key)) + "\x00", nil
})

func GetUnCommonHeader(ctx context.Context, key string) string {
    phpHeaderKey, err := headerKeyCache.Get(ctx, key, loader)
    ...
}
```

## Benchmarks

Apple M1 Pro, 8 runs, `benchstat` comparison — no regressions, no extra
allocations:

| Benchmark | main | PR | vs base |
|---|---|---|---|
| HelloWorld | 41.81µ ± 2% | 42.75µ ± 5% | ~ (p=0.065) |
| ServerSuperGlobal | 73.36µ ± 2% | 74.20µ ± 3% | ~ (p=0.105) |
| UncommonHeaders | 69.03µ ± 3% | 68.71µ ± 1% | ~ (p=0.382) |

All results within noise. Zero change in allocations.
2026-03-10 19:07:32 +01:00
dependabot[bot] 2b27601994 ci: bump the github-actions group with 3 updates
Bumps the github-actions group with 3 updates: [actions/upload-artifact](https://github.com/actions/upload-artifact), [actions/download-artifact](https://github.com/actions/download-artifact) and [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance).


Updates `actions/upload-artifact` from 6 to 7
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v6...v7)

Updates `actions/download-artifact` from 7 to 8
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v7...v8)

Updates `actions/attest-build-provenance` from 3 to 4
- [Release notes](https://github.com/actions/attest-build-provenance/releases)
- [Changelog](https://github.com/actions/attest-build-provenance/blob/main/RELEASE.md)
- [Commits](https://github.com/actions/attest-build-provenance/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: actions/download-artifact
  dependency-version: '8'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: actions/attest-build-provenance
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-10 19:07:00 +01:00
Alexander Stecher d5fa4cf474 fix: doc translation triggers (#2267)
Prevents PRs like #2259 by fixing the `git diff` so it ignores nested translation files.
2026-03-10 16:16:56 +01:00
Matthieu Gostiaux 53dfd2fcce add fr trad for hot-reload.md (#2105)
Hello, there doesn't seem to be an open MR for translating the new hot
reload feature. Here's what I can offer.

---------

Signed-off-by: Alexandre Daubois <2144837+alexandre-daubois@users.noreply.github.com>
Signed-off-by: Kévin Dunglas <kevin@dunglas.fr>
Co-authored-by: Alexandre Daubois <2144837+alexandre-daubois@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Kévin Dunglas <kevin@dunglas.fr>
2026-03-10 10:55:29 +01:00
Kévin Dunglas 2728df98c9 chore: prepare release 1.12.1 caddy/v1.12.1 v1.12.1 2026-03-10 10:49:02 +01:00
Kévin Dunglas ff70f7e02b chore: bump deps 2026-03-10 10:33:03 +01:00
Kévin Dunglas b49189ebf2 docs: improve hot reload, add missing features (#2261)
Updated wording for clarity and consistency in the hot reload
documentation.

---------

Signed-off-by: Kévin Dunglas <kevin@dunglas.fr>
Co-authored-by: Alexandre Daubois <2144837+alexandre-daubois@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-09 16:58:11 +01:00
Kévin Dunglas 808757c52b docs: revise bug report template for improved instructions (#2181)
Updated bug report template for clarity and detail.

---------

Signed-off-by: Kévin Dunglas <kevin@dunglas.fr>
Co-authored-by: henderkes <m@pyc.ac>
2026-03-09 15:55:51 +01:00
Kévin Dunglas 16a70e4f10 ci: simplify translate workflow by using gh instead of peter-evans/create-pull-request (#2237) 2026-03-09 15:55:15 +01:00
Alexander Stecher 1f484321a0 tests: opcache_preload (#2257)
Adds a test to reproduce #2254 and verify `opcache_preload` works as
intended with the changes from
https://github.com/php/frankenphp/pull/2252.
2026-03-09 15:54:36 +01:00
github-actions[bot] c1e30cd638 docs: update translations (#2216)
Translation updates for: extension-workers.md .

---------

Co-authored-by: alexandre-daubois <2144837+alexandre-daubois@users.noreply.github.com>
Co-authored-by: Alexandre Daubois <alex.daubois@gmail.com>
2026-03-09 15:13:46 +01:00
Marc d72a9ee9cb add vcpkg installed dir to gitignore (#2258) 2026-03-09 20:25:54 +07:00
github-actions[bot] 0774b141c9 docs: update translations (#2241)
Translation updates for: config.md .

---------

Signed-off-by: Kévin Dunglas <kevin@dunglas.fr>
Co-authored-by: dunglas <57224+dunglas@users.noreply.github.com>
Co-authored-by: Kévin Dunglas <kevin@dunglas.fr>
2026-03-09 14:18:13 +01:00
github-actions[bot] 9008b7bb14 docs: update translations (#2230)
Translation updates for: docker.md .

---------

Signed-off-by: Marc <m@pyc.ac>
Signed-off-by: Alexandre Daubois <2144837+alexandre-daubois@users.noreply.github.com>
Co-authored-by: alexandre-daubois <2144837+alexandre-daubois@users.noreply.github.com>
Co-authored-by: Marc <m@pyc.ac>
Co-authored-by: Alexandre Daubois <alex.daubois@gmail.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-09 14:09:31 +01:00
github-actions[bot] ebc7ef0868 docs: update translations (#2176)
Translation updates for: performance.md .

---------

Co-authored-by: AlliBalliBaba <45872305+AlliBalliBaba@users.noreply.github.com>
Co-authored-by: Alliballibaba <alliballibaba@gmail.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: alexandre-daubois <2144837+alexandre-daubois@users.noreply.github.com>
2026-03-09 14:08:14 +01:00
github-actions[bot] 48a9c08ee0 docs: update translations (#2206)
Translation updates for: worker.md .

---------

Signed-off-by: Alexandre Daubois <2144837+alexandre-daubois@users.noreply.github.com>
Co-authored-by: dunglas <57224+dunglas@users.noreply.github.com>
Co-authored-by: Alexandre Daubois <2144837+alexandre-daubois@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Alexandre Daubois <alex.daubois@gmail.com>
2026-03-09 14:07:59 +01:00
Marc d1fcf0656c fix sigsev on bind permissions denied (#2250?) (#2251)
Previous behaviour was bugged from somewhere between 1.11.3 and 1.12.0:

```bash
❯❯ frankenphp git:(main) 10:46 ./frankenphp php-server
2026/03/08 03:46:36.541 WARN    admin   admin endpoint disabled
2026/03/08 03:46:36.541 WARN    http.auto_https server is listening only on the HTTP port, so no automatic HTTPS will be applied to this server {"server_name": "php", "http_port": 80}
2026/03/08 03:46:36.542 INFO    tls.cache.maintenance   started background certificate maintenance      {"cache": "0x182aaec16500"}
2026/03/08 03:46:36.582 INFO    frankenphp      FrankenPHP started 🐘   {"php_version": "8.6.0-dev", "num_threads": 32, "max_threads": 32}
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x30 pc=0xb436bf]

goroutine 1 [running]:
github.com/caddyserver/caddy/v2.(*Context).Value(0x182aaedbe078?, {0x1e2d340?, 0x25e0c90?})
        <autogenerated>:1 +0x1f
github.com/caddyserver/caddy/v2/modules/caddyhttp.(*extraFieldsSlogHandler).Handle(_, {_, _}, {{0xc2635a2724e776c5, 0x67b02fd, 0x3ca5000}, {0x22f7b13, 0x17}, 0x0, 0x1b7943e, ...})
        /home/m/go/pkg/mod/github.com/caddyserver/caddy/v2@v2.11.2/modules/caddyhttp/logging.go:308 +0x4c
log/slog.(*Logger).logAttrs(0x182aaea2bc30, {0x260a170?, 0x182aae96ad20?}, 0x0, {0x22f7b13, 0x17}, {0x0, 0x0, 0x0})
        /home/m/static-php-cli/pkgroot/x86_64-linux/go-xcaddy/src/log/slog/logger.go:276 +0x277
log/slog.(*Logger).LogAttrs(...)
        /home/m/static-php-cli/pkgroot/x86_64-linux/go-xcaddy/src/log/slog/logger.go:194
github.com/dunglas/frankenphp/caddy.(*FrankenPHPApp).Stop(0x182aae844300)
        /home/m/frankenphp/caddy/app.go:182 +0x14b
github.com/caddyserver/caddy/v2.run.func2(...)
        /home/m/go/pkg/mod/github.com/caddyserver/caddy/v2@v2.11.2/caddy.go:460
github.com/caddyserver/caddy/v2.run(0x25d1e48?, 0x1)
        /home/m/go/pkg/mod/github.com/caddyserver/caddy/v2@v2.11.2/caddy.go:471 +0x977
github.com/caddyserver/caddy/v2.unsyncedDecodeAndRun({0x182aaee76400, 0x3ad, 0x400}, 0x1)
        /home/m/go/pkg/mod/github.com/caddyserver/caddy/v2@v2.11.2/caddy.go:364 +0x17a
github.com/caddyserver/caddy/v2.changeConfig({0x22c19cb, 0x4}, {0x22c7b94, 0x7}, {0x182aaee76000, 0x3ad, 0x400}, {0x0, 0x0}, 0x1)
        /home/m/go/pkg/mod/github.com/caddyserver/caddy/v2@v2.11.2/caddy.go:248 +0x959
github.com/caddyserver/caddy/v2.Load({0x182aaee76000, 0x3ad, 0x400}, 0x1)
        /home/m/go/pkg/mod/github.com/caddyserver/caddy/v2@v2.11.2/caddy.go:137 +0x225
github.com/caddyserver/caddy/v2.Run(0x1fdb7a0?)
        /home/m/go/pkg/mod/github.com/caddyserver/caddy/v2@v2.11.2/caddy.go:109 +0x3b
github.com/dunglas/frankenphp/caddy.cmdPHPServer({0x0?})
        /home/m/frankenphp/caddy/php-server.go:320 +0x27b6
github.com/dunglas/frankenphp/caddy.init.4.func1.WrapCommandFuncForCobra.1(0x182aaee53808, {0x22c1a77?, 0x4?, 0x22c1a03?})
        /home/m/go/pkg/mod/github.com/caddyserver/caddy/v2@v2.11.2/cmd/cobra.go:151 +0x2f
github.com/spf13/cobra.(*Command).execute(0x182aaee53808, {0x3cc96a0, 0x0, 0x0})
        /home/m/go/pkg/mod/github.com/spf13/cobra@v1.10.2/command.go:1015 +0xb14
github.com/spf13/cobra.(*Command).ExecuteC(0x182aae7fbb08)
        /home/m/go/pkg/mod/github.com/spf13/cobra@v1.10.2/command.go:1148 +0x465
github.com/spf13/cobra.(*Command).Execute(...)
        /home/m/go/pkg/mod/github.com/spf13/cobra@v1.10.2/command.go:1071
github.com/caddyserver/caddy/v2/cmd.Main()
        /home/m/go/pkg/mod/github.com/caddyserver/caddy/v2@v2.11.2/cmd/main.go:72 +0x65
main.main()
        /home/m/frankenphp/caddy/frankenphp/main.go:14 +0xf
```

This restores it to exit cleanly again:

```bash
        /home/m/frankenphp/caddy/frankenphp/main.go:14 +0xf
❯❯ frankenphp git:(main)  10:46 go build 
❯❯ frankenphp git:(main) 10:58 ./frankenphp php-server
2026/03/08 03:58:01.533 WARN    admin   admin endpoint disabled
2026/03/08 03:58:01.533 WARN    http.auto_https server is listening only on the HTTP port, so no automatic HTTPS will be applied to this server {"server_name": "php", "http_port": 80}
2026/03/08 03:58:01.533 INFO    tls.cache.maintenance   started background certificate maintenance      {"cache": "0x850ebf79280"}
2026/03/08 03:58:01.556 INFO    tls.cache.maintenance   stopped background certificate maintenance      {"cache": "0x850ebf79280"}
2026/03/08 03:58:01.556 INFO    http    servers shutting down with eternal grace period
Error: loading new config: http app module: start: listening on :80: listen tcp :80: bind: permission denied
```
2026-03-09 18:32:22 +07:00