How Symfony Decides a Request Is Secure
Most HTTPS problems in Symfony are not certificate problems. They are scheme-detection problems. Symfony builds absolute URLs, sets secure cookies, and triggers HTTP-to-HTTPS redirects based on what the Request object reports as the current scheme. On a Managed Cloud shared server running LiteSpeed, TLS is terminated at the web server, and PHP runs as a separate process behind it through LiteSpeed's SAPI. Your application code frequently receives the request as plain HTTP internally, even though the visitor connected over a valid HTTPS connection.
When that happens, $request->isSecure() returns false, the router generates http:// links in emails and templates, and any HTTPS enforcement you have configured can loop endlessly because Symfony keeps thinking the connection is insecure and keeps redirecting. The browser follows the redirect over HTTPS, LiteSpeed hands PHP a request that still looks like HTTP, and the cycle repeats until you see ERR_TOO_MANY_REDIRECTS.
The web server communicates the real scheme through headers such as X-Forwarded-Proto and, in some LiteSpeed configurations, the HTTPS server variable. Symfony ignores forwarded headers by default as a security measure, because a malicious client could otherwise spoof them. You have to explicitly tell the framework which proxies to trust. Since you cannot edit server-wide configuration on shared hosting, all of this is handled inside your project directory, environment variables, and the public/.htaccess file. Understanding that the certificate and the application logic are two independent layers keeps you from chasing the wrong fix.
Issuing and Verifying the Certificate
Start with the transport layer so the browser trusts the connection before you touch application code. On the Hostiso stack, AutoSSL (cPanel) or Let's Encrypt (DirectAdmin) issues free domain-validated certificates automatically once DNS points to the server.
In cPanel Jupiter, open Security → SSL/TLS Status. You will see every domain and subdomain with a status indicator. Select the domains that show no certificate and click Run AutoSSL. Provisioning usually completes within a few minutes. If a domain fails, the most common cause is that the DNS A record does not resolve to this server yet, or an existing .htaccess rule blocks the /.well-known/acme-challenge/ path used for validation.
In DirectAdmin Evolution, go to Account Manager → SSL Certificates, choose Free & automatic certificate from ACME Provider, tick both the root domain and the www variant, and save. DirectAdmin also exposes a Force SSL with https redirect checkbox on the same page; leave it unchecked for now, because you want Symfony to own the redirect logic and avoid two competing redirect layers.
Confirm the certificate is live before moving on. Load https://yourdomain.com directly and check for the padlock. If the ACME challenge keeps failing, temporarily add an exclusion at the top of public/.htaccess so validation files are always reachable:
RewriteEngine On
RewriteCond %{REQUEST_URI} ^/\.well-known/acme-challenge/ [NC]
RewriteRule ^ - [L]Place that block above any existing rewrite rules. Once AutoSSL succeeds, the rule is harmless to keep and prevents future renewal failures.
Making Symfony Proxy-Aware and Forcing HTTPS
With a trusted certificate in place, tell Symfony to trust the scheme LiteSpeed forwards. Modern Symfony reads this from the TRUSTED_PROXIES and TRUSTED_HEADERS environment variables, which you can set without shell access by editing .env.local in your project root through cPanel or DirectAdmin File Manager. Because LiteSpeed terminates TLS on the same machine, the safe and simple value trusts the loopback and private ranges:
# .env.local
APP_ENV=prod
APP_DEBUG=0
TRUSTED_PROXIES=127.0.0.1,REMOTE_ADDR,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16
TRUSTED_HEADERS=x-forwarded-for,x-forwarded-host,x-forwarded-proto,x-forwarded-portThe special token REMOTE_ADDR tells Symfony to trust whatever address is directly in front of it, which is exactly the local web server on shared hosting. In older projects where trusted proxies are configured in public/index.php instead, add Request::setTrustedProxies() with the same values near the top of the front controller.
Next, decide where the redirect happens. You have two clean options, and you should pick only one to avoid loops. The lighter approach is to let LiteSpeed handle the redirect in public/.htaccess before PHP ever runs, which is faster and sidesteps scheme-detection entirely for the redirect itself:
RewriteEngine On
RewriteCond %{HTTPS} !=on
RewriteCond %{HTTP:X-Forwarded-Proto} !https
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]Checking both %{HTTPS} and the forwarded header covers every way LiteSpeed might report the scheme, which is what prevents the redirect loop. Keep this block below the ACME exclusion and above Symfony's default front-controller rewrite.
The alternative is to enforce HTTPS inside Symfony using access control in config/packages/security.yaml, which is useful when you need HTTPS only on certain routes such as login or checkout:
security:
access_control:
- { path: ^/, roles: PUBLIC_ACCESS, requires_channel: https }This path depends entirely on trusted proxies being configured correctly. Without the .env.local changes above, requires_channel: https will loop forever. For most sites, the .htaccess redirect plus correct trusted proxies is the most reliable combination.
After editing environment variables, clear the compiled container so the new settings take effect. In File Manager, delete the contents of var/cache/prod/. Symfony rebuilds the cache on the next request. If your account uses OPcache through the cPanel PHP Selector, you may also need to reset it; a quick way is to bump the deployment by touching public/index.php or using the LiteSpeed cache purge in the control panel if the LSCache extension is active.
Clearing Mixed-Content Warnings and Verifying
Once the padlock appears but the browser still shows "Not fully secure," you have mixed content: HTTPS pages requesting HTTP assets. In Symfony this usually comes from three places. Absolute URLs stored in the database or in templates with a hardcoded http:// prefix; the router generating http:// links because trusted proxies were still misconfigured when a page was cached; and third-party asset URLs pinned to HTTP.
Fix template links first by using scheme-relative or router-generated URLs. In Twig, prefer {{ asset('build/app.css') }} and {{ path('route_name') }} for relative links, and {{ url('route_name') }} only where an absolute URL is genuinely needed. With trusted proxies working, url() now emits https:// automatically. For assets served from a CDN or external host, edit the reference to use https:// explicitly rather than a protocol-relative //, which some older mail clients mishandle.
To catch remaining offenders, open the browser developer console on the affected page; every blocked or insecure request is listed with its full URL, which points you straight to the template or database row to correct. For content already saved in a database with hardcoded links, use phpMyAdmin to run a targeted update on the relevant table and column, for example replacing http://yourdomain.com with https://yourdomain.com in a content field, after taking an export backup first.
Finally, verify the whole chain. Load the site in a private window to avoid cached redirects, confirm the padlock, and check that http:// requests land on https:// with a single 301 rather than a chain. If anything misbehaves, your local var/log/prod.log and the error_log file in your document root will show scheme-related exceptions or redirect diagnostics. Similar caching and OPcache considerations apply across PHP frameworks on this platform, as covered in our CodeIgniter performance tuning guide. With the certificate issued, proxies trusted, one redirect layer chosen, and asset URLs normalized, Symfony serves cleanly over HTTPS end to end.