simonetti/ping-bundle
A lightweight Symfony bundle that adds a simple ping endpoint for health checks and uptime monitoring. It offers straightforward configuration and integration, helping confirm that a Symfony application is reachable and responsive.
First, calibrate expectations: this is a Symfony bundle, not a Laravel package. If you're a Laravel developer, simonetti/ping-bundle will not install into a Laravel app — it ships a Bundle subclass, a DependencyInjection extension, and service definitions intended for Symfony's kernel. Treat it as (a) a reference implementation for a health-check endpoint you can replicate in Laravel in ~15 lines, or (b) something you'd use only when working inside a Symfony codebase.
Assuming a Symfony host app:
composer require simonetti/ping-bundle — v4.0 (PR #13, "update 6 & 7") refreshed the framework constraints to Symfony 6 and 7, so on a modern host app the dependency resolution should now just work; the old --with-all-dependencies / constraint-forking dance is no longer needed. Two caveats: v4.0 is a major bump, so it almost certainly dropped Symfony 5.x (stay on ^3.1 if you're still there), and the Symfony 7 path implies a PHP 8.2+ floor — check what composer resolves before you commit. Symfony 8 is not covered.config/bundles.php — if the recipe isn't available (still no Flex recipe at this star count), you add the line manually: Simonetti\PingBundle\PingBundle::class => ['all' => true].bin/console debug:router | grep -i ping. The whole value proposition is one route returning a 200; if it isn't there, nothing else in the package matters.curl -i http://localhost:8000/ping.Where to look first, in this order: README.md (likely thin) → the bundle class (getContainerExtension, and whether it implements PrependExtensionInterface) → Resources/config/routing.* or an attribute-routed controller → the DependencyInjection/Configuration.php tree. The Configuration class is the source of truth for what you can actually toggle; the README usually isn't. v4.0's diff is worth a two-minute read since it touched service definitions to satisfy Symfony 7 — attribute-routed controllers and autowiring aliases are the usual casualties of that bump.
First real use case: wiring it as a Kubernetes livenessProbe / readinessProbe or an AWS ALB target-group health check. That's the only thing a package this size is good for, and it's a legitimately useful one.
Liveness vs readiness separation. A bare ping proves the PHP process answers — that's liveness. It proves nothing about the database, Redis, or message broker. The pattern that survives production:
/ping (this bundle) → liveness only. Never let it touch I/O; a slow DB must not cause the orchestrator to kill a healthy pod and cascade./health/ready → your own controller that checks Connection::ping() on Doctrine, a Redis PING, and queue depth. Return 503 on failure.Keep the bundle's endpoint dumb and put all dependency checks behind a separate route you own, so you control timeouts and can cache the result.
Deploy-gate usage. The second-most-common pattern is blocking a deploy until the new version is serving:
curl -fsS --retry 10 --retry-delay 2 --retry-connrefused https://app.example.com/ping
-f is essential — without it curl exits 0 on a 500 and your pipeline goes green on a broken release.
Wrapping rather than consuming directly. Don't let application code call the ping route. If you need programmatic health state, define a small HealthCheckerInterface in your app and decorate the bundle's service (see extension points below). This keeps the third-party surface to exactly one class and makes ripping the bundle out a one-line change.
Upgrading 3.x → 4.0. Do it as its own PR with nothing else in it. The release is a single compat commit, so the functional risk is low, but a major bump on a package with no test suite you can see means you want a clean revert point. Run composer update simonetti/ping-bundle --with-all-dependencies in a branch, clear the container, hit /ping, and diff debug:container | grep -i ping before and after to catch a renamed service id.
If you're actually on Laravel, replicate rather than integrate:
// routes/web.php
Route::get('/ping', fn () => response('pong', 200))->withoutMiddleware(['web']);
Put it outside the web middleware group so you don't drag in sessions and CSRF, and add a separate /health/ready closure hitting DB::select('select 1') and Cache::store('redis')->get('probe') behind a short cache. That's functionally identical to what this bundle gives a Symfony app.
Integration tip: mount the ping route under a path your ingress/load balancer already exposes unauthenticated, and confirm it isn't caught by your security.yaml firewall's access_control — a ping that 302s to a login form reads as a failure to most probes and is a classic first-hour mistake.
404 is the default failure mode, and it has three causes. The bundle isn't in config/bundles.php; the routes weren't imported (some bundles require an explicit ping: { resource: ... } entry in config/routes.yaml rather than auto-registering); or a catch-all route is shadowing it. Diagnose with bin/console debug:router --show-controllers — don't debug by reading vendor source first.
Route prefix and method. Health probes are usually GET. If the bundle only registers one method and your ALB uses HEAD, you'll get 405s that look like outages. Check debug:router output for the accepted methods.
Adoption risk — revised, not resolved. v4.0 materially improves the picture: someone is still doing compatibility maintenance, and Symfony 6/7 support means you're not fork-patching constraints anymore. But calibrate what kind of maintenance this is — a single "update 6 & 7" commit per framework cycle, not a security-response cadence, not a feature roadmap, and still one star with zero dependents you can point to. Concrete posture: (1) pin ^4.0 (not *, not dev-main), (2) commit composer.lock, (3) watch for the Symfony 8 bump landing before you upgrade your host app rather than after, and (4) keep a manual kubectl exec curl localhost/ping escape hatch documented in your runbook so the endpoint is never the only thing between you and a rolling deploy.
Version skew across a fleet is now a real hazard. If v4.0 dropped Symfony 5, a monorepo with mixed Symfony versions will resolve different ping-bundle majors per package, and your service ids/config shape may diverge. Pin per-package and verify debug:router in each app after the split.
Leaking information through the response body. A health endpoint that returns version strings, PHP version, or exception messages is reconnaissance for free. If the bundle echoes anything beyond a fixed token, override the response in your own controller or strip the body at the proxy. The Symfony 7 refresh is a good moment to re-check this — compat PRs sometimes swap a bespoke response for a framework helper.
Proxies and caching. A CDN or reverse proxy in front of /ping that caches a 200 will happily serve that 200 while your origin is dead — the exact opposite of what you want. Send Cache-Control: no-store and confirm your ingress doesn't have a default TTL rule.
Extension points, in order of safety:
#[AsDecorator('simonetti_ping.service_id')] (get the real id from debug:container --show-private | grep -i ping after upgrading, because compat bumps rename these more often than you'd like) to wrap behavior without forking.Configuration.php tree exposes; usually just a path or status code.Debugging checklist: bin/console debug:router (does the route exist?), debug:container | grep -i ping (is the service registered?), bin/console cache:clear (Symfony container caches bundle registration aggressively — a newly added bundle in bundles.php, or a freshly upgraded major, won't appear until you clear in dev), and finally check var/log/dev.log for a ServiceNotFoundException from a missing symfony/http-kernel or routing sub-dependency. On the v4.0 upgrade specifically, if the bundle silently no-ops, first suspect a stale var/cache container compiled against 3.x service definitions.
Verdict for a day-to-day assessment: still budget an hour, not a sprint. v4.0 removes the main reason to avoid it — you can now composer require on Symfony 6/7 without forking — so "vendor it, wrap it, pin it" is a defensible posture rather than a compromise. Keep a 15-line in-house replacement sketched anyway; the functional requirement (return 200 OK) remains too trivial to justify deep coupling, and the compatibility ceiling will reach you again at Symfony 8.
How can I help you explore Laravel packages today?