Server rack in a data center representing reverse proxy and load balancer infrastructure

WordPress is_ssl() Returns False? Reverse Proxy Fix

You are staring at an error message that says the native WordPress function is_ssl() returned false. Your site loads over HTTPS in the browser. The padlock is there. The certificate is valid. But WordPress refuses to acknowledge the connection is secure, and now your integration is broken: Application Passwords will not appear, REST API authentication fails, admin redirects loop, or your content automation plugin cannot publish. Here is exactly what is wrong and how to fix it in wp-config.php.

What Does “is_ssl() Returned False” Actually Mean?

is_ssl() is a WordPress core function in wp-includes/load.php that checks two server variables to determine whether the current request arrived over HTTPS: $_SERVER['HTTPS'] (which must be 'on' or '1') and $_SERVER['SERVER_PORT'] (which must be 443). If neither condition is met, the function returns false, and WordPress treats the request as plain HTTP regardless of what the browser sees. The WordPress developer documentation confirms this and explicitly notes that the function does not work reliably behind load balancers and reverse proxies.

The function source is straightforward:

function is_ssl() {
    if ( isset( $_SERVER['HTTPS'] ) ) {
        if ( 'on' === strtolower( $_SERVER['HTTPS'] ) ) {
            return true;
        }
        if ( '1' === (string) $_SERVER['HTTPS'] ) {
            return true;
        }
    } elseif ( isset( $_SERVER['SERVER_PORT'] ) && ( '443' === (string) $_SERVER['SERVER_PORT'] ) ) {
        return true;
    }
    return false;
}

Notice what is missing: there is no check for HTTP_X_FORWARDED_PROTO or any other forwarded header. WordPress core does not look at proxy headers. If your traffic passes through a reverse proxy, load balancer, or CDN that terminates TLS and forwards the request to your origin over plain HTTP, $_SERVER['HTTPS'] is never set to 'on' and $_SERVER['SERVER_PORT'] is 80, not 443. The function returns false even though the visitor is on a fully encrypted connection.

Why Does is_ssl() Return False When the Site Loads Over HTTPS?

An open padlock resting on a laptop keyboard, representing a broken SSL detection that fails to recognize an encrypted connection

The most common cause: a reverse proxy, load balancer, or CDN terminates the TLS connection before the request reaches WordPress. The browser talks HTTPS to the proxy. The proxy talks HTTP to your origin server. WordPress only sees the HTTP hop, so is_ssl() returns false. This affects every layer that depends on SSL detection: Application Passwords, REST API authentication, admin SSL enforcement, canonical redirects, and secure cookie flags.

If you are debugging a REST API integration failure, this is almost certainly your root cause. WordPress hides Application Passwords on sites where is_ssl() returns false, which means your content automation tool cannot authenticate. We cover the full Application Passwords diagnostic chain in our Application Passwords troubleshooting guide, but if you are here, the SSL detection layer is where to start.

The table below maps your symptom to the likely cause and where to jump in this guide.

SymptomLikely CauseJump To
Site loads HTTPS in browser, is_ssl() returns falseReverse proxy or load balancer terminates TLSCause 1
ERR_TOO_MANY_REDIRECTS after enabling CloudflareCloudflare Flexible SSL modeCause 2
Using CloudFront, Sucuri, or other CDNCDN-specific forwarded header not handledCause 3
Proxy is not sending any forwarded headerProxy configuration missing X-Forwarded-ProtoCause 4
No proxy involved, direct HTTPS, still falseSSL not properly configured at server levelWhere the fix does not apply

How Do I Fix is_ssl() Behind a Reverse Proxy or Load Balancer?

Code editor on a dark screen showing PHP configuration code, representing editing wp-config.php to fix is_ssl() detection behind a reverse proxy

Add a conditional block to wp-config.php that checks the HTTP_X_FORWARDED_PROTO header and sets $_SERVER['HTTPS'] to 'on' when the proxy reports the original request was HTTPS. This is the official fix documented in the WordPress administration handbook and the function reference on developer.wordpress.org. The code must go in wp-config.php, not in a theme or plugin, because it needs to run before WordPress loads its core settings.

What Code Goes in wp-config.php?

Open wp-config.php and add the following block above the line that reads /* That's all, stop editing! Happy publishing. */ and above the require_once ABSPATH . 'wp-settings.php'; line. The placement is critical: if the code runs after WordPress loads, it has no effect.

define( 'FORCE_SSL_ADMIN', true );

if ( isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] )
    && false !== strpos( $_SERVER['HTTP_X_FORWARDED_PROTO'], 'https' )
) {
    $_SERVER['HTTPS'] = 'on';
}

The strpos check instead of a strict equality check handles a specific edge case: when requests pass through multiple proxies, the header can contain a comma-separated list like http,https. A strict === 'https' comparison would fail on that value. The strpos approach finds https anywhere in the string and returns true.

FORCE_SSL_ADMIN forces the admin dashboard to use HTTPS. Without it, WordPress may redirect you to the HTTP version of wp-admin even after the $_SERVER['HTTPS'] fix, creating a redirect loop if the proxy also enforces HTTPS. Set it together with the header check.

How Do I Configure Nginx to Forward the Proto Header?

The wp-config.php fix only works if your reverse proxy actually sends the X-Forwarded-Proto header. If you control the Nginx reverse proxy configuration, make sure the proxy location block includes the proxy_set_header X-Forwarded-Proto $scheme; directive. The $scheme variable resolves to https when the original request was HTTPS.

location / {
    proxy_pass http://wordpress-origin;
    proxy_set_header Host $http_host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_redirect off;
}

After updating the Nginx config, test and reload:

sudo nginx -t
sudo systemctl reload nginx

Can I Fix This at the Apache Level Instead?

Yes. If you are on Apache and prefer not to edit wp-config.php, you can set the HTTPS environment variable at the server level using mod_setenvif. Add this to your Apache virtual host configuration for the origin (the port 80 virtual host that receives proxied requests):

<IfModule mod_setenvif.c>
    SetEnvIf X-Forwarded-Proto "^https$" HTTPS=on
</IfModule>

This sets $_SERVER['HTTPS'] to on before PHP executes, so is_ssl() returns true without any WordPress-level code. The result is identical to the wp-config.php fix, but handled at the web server layer. Use whichever approach you can actually modify in your hosting environment.

How Do I Fix is_ssl() With Cloudflare Flexible SSL?

High-density fiber optic network switch with aqua cables representing CDN and load balancer infrastructure like Cloudflare and CloudFront

Cloudflare Flexible SSL is a specific and extremely common variant of the reverse proxy problem. In Flexible mode, visitors connect to Cloudflare over HTTPS, but Cloudflare connects to your origin server over plain HTTP. WordPress sees the HTTP connection, is_ssl() returns false, and if you have any HTTPS redirect in place (either in WordPress, .htaccess, or a plugin), the result is an ERR_TOO_MANY_REDIRECTS loop: WordPress redirects to HTTPS, Cloudflare requests HTTP again, WordPress redirects again, forever.

Why Does Cloudflare Flexible SSL Cause Redirect Loops?

The redirect loop is caused by a mismatch between what Cloudflare sends to your origin and what WordPress expects. Cloudflare sends the X-Forwarded-Proto: https header to indicate the visitor is on HTTPS, but WordPress ignores that header and looks only at $_SERVER['HTTPS'] and $_SERVER['SERVER_PORT'], both of which reflect the HTTP hop from Cloudflare to your origin. The same wp-config.php fix from Cause 1 resolves this:

if ( isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] )
    && false !== strpos( $_SERVER['HTTP_X_FORWARDED_PROTO'], 'https' )
) {
    $_SERVER['HTTPS'] = 'on';
}

After adding this, WordPress recognizes the request as HTTPS and stops redirecting. The redirect loop breaks immediately.

When Should I Switch to Full (Strict) Mode?

Flexible SSL is a workaround, not a proper configuration. Cloudflare connects to your origin over unencrypted HTTP, which means traffic between Cloudflare and your server is not protected. The long-term fix is to install a real SSL certificate on your origin (Cloudflare offers free Origin Certificates, or you can use Let’s Encrypt) and switch Cloudflare’s SSL/TLS mode from Flexible to Full (Strict). In Full (Strict) mode, Cloudflare connects to your origin over HTTPS, and WordPress sees real HTTPS end-to-end. At that point, you can remove the wp-config.php workaround entirely because $_SERVER['HTTPS'] will be set natively by your web server.

To make the switch: go to Cloudflare dashboard, SSL/TLS, Overview, and change the encryption mode to Full (Strict). Then install the origin certificate on your server. If you use Cloudflare’s Origin Certificate, it is free and valid for 15 years, but only trusted by Cloudflare’s edge servers, not by browsers or other clients. That is fine for the Cloudflare-to-origin connection. For broader compatibility, use Let’s Encrypt via certbot.

What About CloudFront, Sucuri, and Other CDNs?

Different proxies send different headers. The wp-config.php fix for HTTP_X_FORWARDED_PROTO covers Cloudflare, AWS Application Load Balancer, Nginx reverse proxy, and most standard proxy setups. But some CDNs use proprietary headers that the standard check does not catch:

Proxy / CDNHeader to CheckValue
CloudflareHTTP_X_FORWARDED_PROTOhttps
AWS ALBHTTP_X_FORWARDED_PROTOhttps
AWS CloudFrontHTTP_CLOUDFRONT_FORWARDED_PROTOhttps
Cloudflare (alternate)HTTP_CF_VISITOR{“scheme”:”https”}
Sucuri / genericHTTP_X_FORWARDED_SSLon or 1
Some proxiesHTTP_X_PROTOSSL

If your CDN uses a non-standard header, extend the conditional in wp-config.php to check it. Here is a comprehensive version that covers the major proxies, Here is a comprehensive version that covers the major proxies::

if (
    ( isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) && false !== strpos( $_SERVER['HTTP_X_FORWARDED_PROTO'], 'https' ) )
    || ( isset( $_SERVER['HTTP_X_FORWARDED_SSL'] ) && false !== strpos( $_SERVER['HTTP_X_FORWARDED_SSL'], 'on' ) )
    || ( isset( $_SERVER['HTTP_CF_VISITOR'] ) && false !== strpos( $_SERVER['HTTP_CF_VISITOR'], 'https' ) )
    || ( isset( $_SERVER['HTTP_CLOUDFRONT_FORWARDED_PROTO'] ) && false !== strpos( $_SERVER['HTTP_CLOUDFRONT_FORWARDED_PROTO'], 'https' ) )
    || ( isset( $_SERVER['HTTP_X_PROTO'] ) && false !== strpos( $_SERVER['HTTP_X_PROTO'], 'SSL' ) )
) {
    $_SERVER['HTTPS'] = 'on';
}

What If No Proxy Header Is Being Sent at All?

If none of the forwarded headers are present, the conditional checks above will not fire and is_ssl() will keep returning false. This means your proxy is not configured to forward the original protocol. You need to fix the proxy configuration, not WordPress.

Confirm which headers are arriving at PHP by running this WP-CLI command:

wp eval 'var_dump( $_SERVER["HTTP_X_FORWARDED_PROTO"] ?? "not set", $_SERVER["HTTP_X_FORWARDED_SSL"] ?? "not set", $_SERVER["HTTP_CF_VISITOR"] ?? "not set", $_SERVER["HTTP_CLOUDFRONT_FORWARDED_PROTO"] ?? "not set" );'

If every value is "not set", your proxy is stripping or not sending forwarded headers. The fix is in the proxy configuration: ensure X-Forwarded-Proto is set and forwarded to the origin. If you do not control the proxy (for example, a managed hosting provider’s load balancer), contact their support and ask specifically: “Are you forwarding the X-Forwarded-Proto header to the origin, and is the origin receiving it?”

If you cannot get the header forwarded and you are certain your origin is only reachable through the proxy (not directly accessible over the public internet), you can unconditionally set HTTPS:

$_SERVER['HTTPS'] = 'on';

This tells WordPress to treat every request as HTTPS regardless of any header. Only do this if your origin is not directly reachable over HTTP. If an attacker can hit your origin directly on port 80, this unconditional setting would make WordPress emit HTTPS URLs and secure cookies over an insecure connection, which is a security risk.

Where Does the $_SERVER[‘HTTPS’] Fix NOT Apply?

This fix addresses exactly one problem: WordPress cannot detect HTTPS because a proxy terminates TLS before the request reaches PHP. It does not apply to any of the following situations:

  • No proxy involved, direct HTTPS, is_ssl() still false: If your server is directly serving HTTPS without any proxy and is_ssl() returns false, the problem is in your web server configuration. Check that your virtual host is listening on port 443 and that SSL is properly configured. The wp-config.php workaround will mask the problem but will not fix the underlying misconfiguration.
  • No SSL certificate at all: If the site genuinely has no SSL certificate, is_ssl() returning false is correct behavior. The fix is to install a certificate, not to trick WordPress into thinking HTTP is HTTPS. Setting $_SERVER[‘HTTPS’] = ‘on’ without actual encryption means credentials, cookies, and Application Passwords are transmitted in plaintext, which is a security vulnerability.
  • Azure App Services: Some Azure environments do not allow modifying $_SERVER values from wp-config.php. If the conditional block has no effect, the Azure platform is resetting or blocking the assignment. In that case, use the Apache SetEnvIf approach or configure the App Service to forward the header properly.
  • Security plugins overriding is_ssl(): Some security plugins or must-use plugins hook into WordPress and override is_ssl() behavior. If the wp-config.php fix is in place but is_ssl() still returns false, search your codebase: grep -r "is_ssl" wp-content/. A custom override may be forcing the function to return false regardless of $_SERVER[‘HTTPS’].

How Do I Confirm is_ssl() Now Returns True?

After adding the fix and reloading your server configuration, verify with WP-CLI:

wp eval 'var_dump( is_ssl() );'

The output should be bool(true). If it is still bool(false), the header is not arriving or the code is in the wrong place in wp-config.php. Double-check that the block sits above the require_once line and that your proxy is actually sending the header.

You can also verify from the browser. If you were locked out of wp-admin by a redirect loop, try loading https://yoursite.com/wp-admin/. If the dashboard loads without redirecting, the fix worked. Then check the Application Passwords section under Users, Profile: if the form to create a password is now visible (instead of the “HTTPS required” message), SSL detection is working.

For a comprehensive check, add this temporary diagnostic snippet to a must-use plugin or your theme’s functions.php. It outputs an admin notice with the SSL status and all relevant server variables. Remove it after confirming.

add_action( 'admin_notices', function() {
    $diag = sprintf(
        "is_ssl(): %s<br>HTTP_X_FORWARDED_PROTO: %s<br>$_SERVER['HTTPS']: %s<br>$_SERVER['SERVER_PORT']: %s",
        is_ssl() ? 'true' : 'false',
        $_SERVER['HTTP_X_FORWARDED_PROTO'] ?? 'not set',
        $_SERVER['HTTPS'] ?? 'not set',
        $_SERVER['SERVER_PORT'] ?? 'not set'
    );
    echo '<div class="notice notice-info"><p>' . $diag . '</p></div>';
} );

If is_ssl() is now true and you are connecting a content automation tool that authenticates through Application Passwords and the REST API, the SSL detection layer is resolved. If you are still hitting 401 or 403 errors on authenticated REST API requests, the problem has moved to the authentication or routing layer. Our REST API disabled troubleshooting guide covers the full diagnostic chain for those failures, including header stripping, security plugin interference, and server-level blocks. For browser-based integrations that fail after SSL is fixed, the CORS errors guide covers the browser-side layer.

Frequently Asked Questions

Why does WordPress is_ssl() return false when my site is on HTTPS?

The most common reason is a reverse proxy, load balancer, or CDN that terminates the TLS connection and forwards the request to your WordPress origin over plain HTTP. WordPress checks $_SERVER[‘HTTPS’] and $_SERVER[‘SERVER_PORT’], neither of which reflects the encrypted connection the visitor used. The function returns false because it only sees the internal HTTP hop.

What is the wp-config.php fix for is_ssl() behind a reverse proxy?

Add a conditional block to wp-config.php, above the require_once line, that checks the HTTP_X_FORWARDED_PROTO header and sets $_SERVER[‘HTTPS’] = ‘on’ when the header contains ‘https’. Use strpos instead of a strict equality check to handle comma-separated values from multi-proxy chains. Also set define( ‘FORCE_SSL_ADMIN’, true ); to prevent admin redirect loops.

How do I fix the Cloudflare Flexible SSL redirect loop in WordPress?

Cloudflare Flexible SSL connects to your origin over HTTP, so is_ssl() returns false and WordPress redirects to HTTPS, creating an infinite loop. The wp-config.php fix that sets $_SERVER[‘HTTPS’] = ‘on’ based on the X-Forwarded-Proto header resolves the loop. For a permanent fix, install an origin certificate and switch Cloudflare to Full (Strict) mode, which eliminates the HTTP hop entirely.

Where does the $_SERVER HTTPS fix not work?

The fix does not apply when there is no proxy involved and the server is directly misconfigured for SSL, when there is no SSL certificate at all (the real fix is installing one, not tricking WordPress), on Azure App Services where $_SERVER values cannot be modified from wp-config.php, and when a security plugin is overriding is_ssl() with a custom filter that ignores $_SERVER[‘HTTPS’].

How do I test if is_ssl() is now returning true?

Run wp eval ‘var_dump( is_ssl() );’ with WP-CLI. If the output is bool(true), the fix is working. If it is still bool(false), verify the forwarded header is arriving with wp eval ‘var_dump( $_SERVER[“HTTP_X_FORWARDED_PROTO”] ?? “not set” );’ and confirm the code block is above the require_once line in wp-config.php.

Frequently Asked Questions

Why does WordPress is_ssl() return false when my site is on HTTPS?

The most common reason is a reverse proxy, load balancer, or CDN that terminates the TLS connection and forwards the request to your WordPress origin over plain HTTP. WordPress checks $_SERVER[‘HTTPS’] and $_SERVER[‘SERVER_PORT’], neither of which reflects the encrypted connection the visitor used. The function returns false because it only sees the internal HTTP hop.

What is the wp-config.php fix for is_ssl() behind a reverse proxy?

Add a conditional block to wp-config.php, above the require_once line, that checks the HTTP_X_FORWARDED_PROTO header and sets $_SERVER[‘HTTPS’] = ‘on’ when the header contains ‘https’. Use strpos instead of a strict equality check to handle comma-separated values from multi-proxy chains. Also set define( ‘FORCE_SSL_ADMIN’, true ); to prevent admin redirect loops.

How do I fix the Cloudflare Flexible SSL redirect loop in WordPress?

Cloudflare Flexible SSL connects to your origin over HTTP, so is_ssl() returns false and WordPress redirects to HTTPS, creating an infinite loop. The wp-config.php fix that sets $_SERVER[‘HTTPS’] = ‘on’ based on the X-Forwarded-Proto header resolves the loop. For a permanent fix, install an origin certificate and switch Cloudflare to Full (Strict) mode, which eliminates the HTTP hop entirely.

Where does the $_SERVER HTTPS fix not work?

The fix does not apply when there is no proxy involved and the server is directly misconfigured for SSL, when there is no SSL certificate at all (the real fix is installing one, not tricking WordPress), on Azure App Services where $_SERVER values cannot be modified from wp-config.php, and when a security plugin is overriding is_ssl() with a custom filter that ignores $_SERVER[‘HTTPS’].

How do I test if is_ssl() is now returning true?

Run wp eval ‘var_dump( is_ssl() );’ with WP-CLI. If the output is bool(true), the fix is working. If it is still bool(false), verify the forwarded header is arriving with wp eval ‘var_dump( $_SERVER[“HTTP_X_FORWARDED_PROTO”] ?? “not set” );’ and confirm the code block is above the require_once line in wp-config.php.