# Generating HTTPS URLs with Symfony on DigitalOcean App Platform


By Ben Ramsey

Published on May 31, 2026


I've been dealing with a frustrating issue in a [Symfony](https://symfony.com) application hosted on [DigitalOcean App Platform](https://docs.digitalocean.com/products/app-platform/). Since it took me quite a while to find the solution, I thought I'd share it here in case it helps someone else.

By default, every application running on App Platform includes [Cloudflare CDN](https://developers.cloudflare.com/cache/) as a reverse proxy. This provides a number of benefits, especially if your application sets appropriate [`cache-control`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control) and [`etag`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/ETag) headers. Cloudflare will use these to cache your application's responses at [*the edge*](https://deno.com/blog/the-future-of-web-is-on-the-edge), thus reducing load on your servers. So far, so good.

Cloudflare also terminates TLS, which means the traffic is decrypted before being forwarded to DigitalOcean. This presents a problem for my application. It thinks it's receiving traffic over HTTP rather than HTTPS. As a result, Symfony generates URLs with the `http://` scheme rather than `https://`. This isn't a major problem, since everything is configured to redirect to `https://`, but it requires an additional network request, and it's one of those tiny details that will bother me until I figure out how to fix it.

> It's one of those tiny details that will bother me until I figure out how to fix it.
{: .pullquote }

Meanwhile, Symfony's documentation isn't clear about how to solve this. Three pages came up in my search for a solution:

1. [How to Force HTTPS or HTTP for different URLs](https://symfony.com/doc/current/security/force_https.html)
2. [Routing: Forcing HTTPS on Generated URLs](https://symfony.com/doc/current/routing.html#routing-force-https)
3. [How to Configure Symfony to Work behind a Load Balancer or Reverse Proxy](https://symfony.com/doc/current/deployment/proxies.html)

The first isn't relevant to my problem. It's about forcing HTTPS or HTTP for different URLs, not about generating HTTPS URLs when behind a reverse proxy. Plus, it has this scary warning at the bottom of the page:

> Forcing HTTPS while using a reverse proxy or load balancer requires a proper configuration to avoid infinite redirect loops.

The second one indicates it's for use when generating URLs in non-HTTP requests (e.g., from CLI tools, cron jobs, email sending tasks, etc.). It also includes the same scary warning:

> If your server runs behind a proxy that terminates SSL, make sure to [configure Symfony to work behind a proxy](https://symfony.com/doc/current/deployment/proxies.html).
>
> The configuration for the scheme is only used for non-HTTP requests. The schemes option together with incorrect proxy configuration will lead to a redirect loop.

That warning also links to the third page, which *is* relevant to my problem. However, anything involving network configuration and [CIDR notation](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing) has always intimidated me. I was afraid of breaking connectivity to the application, and the only way I could find out whether it worked was to try it in production.

In the end, I needed to make two small changes to my application, neither of which are explicitly documented anywhere:

1. I replaced `x-forwarded-for` with the value of `do-connecting-ip`.

   The [note on using `do-connecting-ip` instead of `x-forwarded-for`](https://docs.digitalocean.com/support/where-can-i-find-the-client-ip-address-of-a-request-connecting-to-my-app/) is buried deep in the DigitalOcean documentation, and if you've never encountered it, it can come as a surprise. I've been around long enough to know this is a common thing that reverse proxies do, so I knew what to look for.

   For Symfony to find the client's correct IP address, you need to rewrite this header early in the request lifecycle. I did this by adding the following to my `index.php` above where the Symfony kernel is instantiated:

   ```php
   // Map DigitalOcean's custom IP header to the standard x-forwarded-for header.
   if (isset($_SERVER['HTTP_DO_CONNECTING_IP'])) {
       $_SERVER['HTTP_X_FORWARDED_FOR'] = $_SERVER['HTTP_DO_CONNECTING_IP'];
   }
   ```

   I only knew to do this because Symfony [shows an example](https://symfony.com/doc/current/deployment/proxies.html#custom-headers-when-using-a-reverse-proxy) with a different `x-forwarded-*` header.

2. I added the following to my `config/packages/framework.yaml` file:

   ```yaml
   framework:
     trusted_proxies: '127.0.0.1,100.64.0.0/10'
     trusted_headers: ['x-forwarded-for', 'x-forwarded-host', 'x-forwarded-proto', 'x-forwarded-port']
   ```
   
   What's important to note here is the value `100.64.0.0/10` in the list of trusted proxies. I could have used `REMOTE_ADDR` as suggested in the documentation, but that felt insecure. I tried using `PRIVATE_SUBNETS`, also suggested in the documentation, but DigitalOcean uses a subnet not found in [Symfony's list of private subnets](https://github.com/symfony/symfony/blob/32389e2808f35532b9d8691e3624dffce92ee3a3/src/Symfony/Component/HttpFoundation/IpUtils.php#L21-L39).

   What's more, I couldn't find this subnet listed in the DigitalOcean documentation. However, it's defined in [RFC 6598](https://www.rfc-editor.org/info/rfc6598/) as the reserved IPv4 prefix for Shared Address Space. I stumbled on it when I noticed the `REMOTE_ADDR` value on requests to my application began with `100.127`, which wasn't in Symfony's list. So I searched to find whether it had any special meaning, and sure enough, it does.
   
After making these changes, `$request->getClientIp()` now returns the correct client IP address instead of the IP address of the ingress proxy. I no longer need to use `$request->getHeaders()->get('do-connecting-ip')` for that value. I am also able to use `{{ url('home') }}` in my Twig templates instead of the hacky and ugly `https://{{ url('home', {}, true) }}` I was using before.


