Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Proxy Url Rewrite Bundle Laravel Package

phpmentors/proxy-url-rewrite-bundle

Symfony bundle that rewrites request URLs behind reverse proxies using forwarded headers, helping PHP applications generate correct absolute links, redirects, and assets when served through load balancers or proxy servers.

View on GitHub
Deep Wiki
Context7

Getting Started

What it solves. When Symfony runs behind a reverse proxy (nginx, HAProxy, AWS ALB, a CDN), the app sees the internal request (http://10.0.0.5:8080) while users see the public one (https://www.example.com). Anything the framework generates from the request — RedirectResponse targets, $this->generateUrl('route', [], UrlGeneratorInterface::ABSOLUTE_URL), form actions, canonical links, OAuth callback URLs, password-reset links in emails — inherits the internal scheme/host/port. phpmentors/proxy-url-rewrite-bundle exists to make those generated URLs match the public-facing proxy URL.

Install and register.

composer require phpmentors/proxy-url-rewrite-bundle

This is a pre-Symfony-Flex-era bundle (last release 2018-07-30, Symfony 3/4 generation). On Symfony 3 you register it in AppKernel::registerBundles(); on Flex it will usually auto-register, but verify config/bundles.php contains the bundle class. Check the composer.json of the bundle against your Symfony version before committing — if you are on Symfony 5+, the native mechanisms (below) are almost certainly a better fit.

Configure it. Add the bundle's config block (confirm the exact key names in DependencyInjection/Configuration.php — this is a small, low-traffic package, so the source, not blog posts, is the source of truth):

# config/packages/phpmentors_proxy_url_rewrite.yaml
phpmentors_proxy_url_rewrite:
    proxy_url: '%env(PROXY_PUBLIC_URL)%'   # e.g. https://www.example.com
PROXY_PUBLIC_URL=https://www.example.com

Set it per environment (config/packages/dev/…, config/packages/prod/…) or drive it from an env var so staging and production differ without code changes.

Where to look first, in order.

  1. README.md — install/registration steps and the config sample.
  2. DependencyInjection/Configuration.php — the actual, authoritative config keys and whether any are required.
  3. The event listener/subscriber class (likely EventListener/…Listener.php or Subscriber/…Subscriber.php) — this tells you what it rewrites (request context vs. response headers/body) and at which kernel event, which determines listener ordering.
  4. Tests/ — the fastest way to see intended behaviour and edge cases.

First use case. Get one redirect working end-to-end. Put the app behind nginx (or a local docker proxy), hit a controller that returns return $this->redirectToRoute('homepage');, and confirm the browser is sent to https://www.example.com/… rather than http://internal-host:8080/…. That single Location header proves the wiring works. Then check a generated absolute URL in a template and a link in an outbound email.

Before you adopt it, know the alternative. Modern Symfony does most of this natively:

// public/index.php
Request::setTrustedProxies(
    ['127.0.0.1', '10.0.0.0/8'],
    Request::HEADER_X_FORWARDED_FOR | Request::HEADER_X_FORWARDED_HOST
    | Request::HEADER_X_FORWARDED_PORT | Request::HEADER_X_FORWARDED_PROTO
);

…plus framework.router.default_uri (Symfony 5.1+) for CLI/worker contexts, and framework.trusted_hosts for host validation. If your proxy already sends correct X-Forwarded-* headers, that combination may cover you entirely. Use this bundle when you cannot rely on headers (e.g. the proxy strips or mangles them) or you need a forced public base URL.

Implementation Patterns

Pattern 1 — Forced public base URL (the main use case). The proxy is opaque and you simply know the public URL. Configure proxy_url statically per environment. This is the lowest-risk configuration: no dependence on which headers the proxy forwards, no trust configuration to get wrong. Use this for redirects, canonical URLs, sitemaps, and email links.

Pattern 2 — Header-driven rewriting. If the proxy sends X-Forwarded-Proto/X-Forwarded-Host, prefer letting Symfony's trusted-proxy support derive the base URL and use the bundle only to force/repair values. Either way, trusted proxies must be configured first — untrusted X-Forwarded-* headers are ignored by Request, and a bundle that reads raw $_SERVER will disagree with the router. Pick one source of truth and document it in the repo.

Pattern 3 — Environment matrix. Keep one config file and vary an env var:

# config/packages/phpmentors_proxy_url_rewrite.yaml
phpmentors_proxy_url_rewrite:
    proxy_url: '%env(resolve:PROXY_PUBLIC_URL)%'
# config/packages/prod/services.yaml (or your deployment env)
parameters:
    env(PROXY_PUBLIC_URL): 'https://www.example.com'

Never hardcode the production host in a file that is also loaded in dev, or developers will chase redirect loops to production.

Pattern 4 — Generate URLs correctly in the first place. Treat the bundle as a safety net, not a substitute for good URL generation:

  • Use $this->generateUrl($route, $params, UrlGeneratorInterface::ABSOLUTE_URL) and Twig's url()/absolute_url() rather than hand-built strings.
  • Return RedirectResponse built from the router, not new RedirectResponse('https://…').
  • For emails and async jobs, use the router in CLI context — see the default_uri gotcha below.

Anything you build as a raw string bypasses the request context and therefore bypasses the bundle.

Pattern 5 — Proxy configuration that pairs with it. On nginx, so headers and bundle agree:

location / {
    proxy_pass http://app:8080;
    proxy_set_header Host              $host;
    proxy_set_header X-Forwarded-Host  $host;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Forwarded-Port  $server_port;
    proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
}

If TLS terminates at the proxy and you don't forward X-Forwarded-Proto, you will get http:// URLs on an https:// page — mixed content and, worse, redirect loops on login.

Pattern 6 — Verify in a functional test. The behaviour is a response header, so it is cheap to lock down:

$client = static::createClient();
$client->request('GET', '/login');
$this->assertStringStartsWith('https://www.example.com/', $client->getResponse()->headers->get('Location'));

Run this in CI with the same config as production; it catches a dropped env var instantly.

Pattern 7 — Migration seam. Because this package is small and unmaintained, isolate it: keep the config in one file, avoid referencing its classes from application code, and record the intended end state (Request::setTrustedProxies + default_uri). That way removing it later is a config change rather than a refactor.

Gotchas and Tips

  • Symfony compatibility is the first risk, not the last. Last release 2018-07-30 and a tiny user base (5 stars, 0 dependents) means no one has exercised it against Symfony 5/6/7, PHP 8.1+, or the current event dispatcher. If composer require resolves, run the full suite before trusting it; if it doesn't resolve, prefer the native approach or copy the listener into your own src/.
  • Trusted proxies are the classic trap. X-Forwarded-* headers are only honoured for proxies listed via Request::setTrustedProxies(). Without that, Symfony (and any bundle reading the Request) still reports the internal host, and you will conclude "the bundle doesn't work" when the real problem is trust configuration. Set it in public/index.php before the kernel handles the request.
  • Listener priority and ordering. Whatever it rewrites (request context or response headers) must run early enough to affect routing and URL generation, and — if it touches the response — before the response is sent. Confirm registration with bin/console debug:event-dispatcher and check the priority; conflicts with other kernel.request/kernel.response listeners (security, locale, CORS) usually manifest as "works on some routes only".
  • Double rewriting. If the proxy already sends a clean Host and you force proxy_url, you can end up with the public host appended twice or a mismatched Location. Pick one mechanism; don't let both the proxy and the bundle rewrite.
  • Relative URLs are untouched. path() and relative generateUrl output are correct by definition. If you only test relative links you'll see "it works" and miss that absolute URLs are still wrong. Grep for absolute URL generation and RedirectResponse when auditing.
  • Hand-built strings bypass everything. new RedirectResponse('/x') is fine (relative); 'https://'.$request->getHost().'/x' is not. Search the codebase for getHost(), getSchemeAndHttpHost(), and string-concatenated URLs.
  • CLI, workers, and email generation have no request. UrlGenerator in a console command or Messenger worker uses framework.router.request_context (router.request_context.host, .scheme, .base_url) or, on Symfony 5.1+, framework.router.default_uri. A request-scoped bundle cannot help here — this is the single most common "the links in my emails point to localhost" bug, and it is not this bundle's job.
  • Cookies and sessions are host-scoped. If framework.session.cookie_domain or framework.trusted_hosts don't match the public host, you get login failures and CSRF mismatches that look like bundle bugs. Align them before debugging the bundle.
  • Asset URLs may use a separate config. framework.assets.base_urls / packages/*.base_url / CDN prefixes are generated outside the router context. If asset URLs are still internal after setup, that's why.
  • Debugging recipe. (1) bin/console debug:config phpmentors_proxy_url_rewrite (or inspect the container) to confirm the value actually loaded; (2) temporarily dump $request->getSchemeAndHttpHost() and the router's RequestContext in a controller; (3) inspect the raw response headers (curl -I) rather than the rendered page; (4) debug:event-dispatcher to confirm the listener is attached.
  • No release cadence means no security patches. Pin the version in composer.json, and if the package is load-bearing, vendor the listener into your own namespace — it is small enough to own outright and removes an unmaintained dependency from your upgrade path.
  • Extension point to know. If the bundle's rewriting logic doesn't match your proxy's behaviour (e.g. path prefix stripping), the cleanest escape hatch is to decorate/override the bundle's service with your own implementation rather than patching vendor/. Compiler passes and service decoration work normally as long as the service ID is stable — another reason to read the bundle's Resources/config/services.xml first.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route