Linux – Error too many redirects in nginx + varnish+ SSL

httpslinuxnginxsslvarnish

I have a clustered infrastructure. I use nginx for SSL termination in front of varnish. The backends of varnish are apache web servers.and I also have a haproxy as a load balancer which sends HTTPS requests directly to nginx and sends HTTP requests directly to varnish servers. The problem is that when I start nginx everything is OK for some while, but after that I get the too_many_error_redirects in browsers when browsing ssl websites!! I think there is something wrong with my configurations, but I don't know which configurations (nginx or varnish) are the cause of this error. When I forward requests directly to the webservers everything is OK, so may be the varnish config has problem. here are my configurations:
Nginx config: domain_name.conf

server {
        listen 443;

        server_name mydomain.com;
        ssl on;

        ssl_certificate /etc/nginx/ssl/domain_name_bundle.pem;
        ssl_certificate_key /etc/nginx/ssl/my_key.key;

        ssl_session_cache shared:SSL:20m;
        ssl_session_timeout 10m;

        ssl_prefer_server_ciphers       on;
        ssl_protocols                   TLSv1 TLSv1.1 TLSv1.2;
        ssl_ciphers "EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EECDH EDH+aRSA RC4 !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS +RC4 RC4";

        add_header Strict-Transport-Security "max-age=31536000";
        server_tokens off;
        proxy_pass_header Server;
location / {
            proxy_pass http://cache-servers;
            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 https;
            proxy_set_header X-Forwarded-Port 443;
            proxy_redirect    off;
            proxy_set_header Host $host;
        }
}
upstream cache-servers
{
        ip_hash;
        #cache servers
        server 192.168.1.11:8080;
        server 192.168.1.12:8080;
}

Varnish config:

vcl 4.0;

import directors;

# Check backend health
probe backend_healthcheck {
   .url = "/";
   .timeout = 10s;
   .window = 5;
   .threshold = 3;
   .interval = 5s;
   .expected_response = 200;

}

backend web1 {
    .host = "192.168.1.105";
    .port = "8080";
    .probe = backend_healthcheck;
}
backend web2 {
    .host = "192.168.1.106";
    .port = "8080";
    .probe = backend_healthcheck;
}
sub vcl_init {
    new apache = directors.round_robin();
    apache.add_backend(web1);
    apache.add_backend(web2);
}
sub vcl_recv {

    set req.backend_hint = apache.backend();

set req.http.X-Forwarded-For = client.ip;

if (req.method == "GET" && (req.url ~ "^/?mylogout=")) {
     unset req.http.Cookie;
      return (pass);
  }
  #we should not cache any page for Prestashop backend
  if (req.method == "GET" && (req.url ~ "^/admin70")) {
      return (pass);
  }
  #we should not cache any page for customers
  if (req.method == "GET" && (req.url ~ "^/authentification" || req.url ~ "^/my-account")) {
      return (pass);
  }
  #we should not cache any page for customers
  if (req.method == "GET" && (req.url ~ "^/identity" || req.url ~ "^/my-account.php")) {
      return (pass);
  }
  #we should not cache any page for sales
  if (req.method == "GET" && (req.url ~ "^/cart.php" || req.url ~ "^/order.php")) {
      return (pass);
  }

#we should not cache any page for sales
  if (req.method == "GET" && (req.url ~ "^/addresses.php" || req.url ~ "^/order-detail.php")) {
      return (pass);
  }
  #we should not cache any page for sales
  if (req.method == "GET" && (req.url ~ "^/order-confirmation.php" || req.url ~ "^/order-return.php")) {
      return (pass);
  }
if (req.method != "GET" && req.method != "HEAD") {
      return (pass);
  }

#pass feeds
  if (req.url ~ "/feed")
  {
        return (pass);
  }

if (req.url ~ "/wp-(login|admin)" || (req.method == "GET" && req.url ~ "^/admin") || (req.method == "GET" && req.url ~ "^/user"))
  {
        #unset req.http.cookie;
        return (pass);
  }

  #cache everything left behind
  return(hash);
}

sub vcl_backend_response {

    if  (!(bereq.url ~ "(wp-(login|admin)|admin)"))  {
      unset beresp.http.set-cookie;
      set beresp.ttl = 10m;
     }
    set beresp.grace = 2h;

}

sub vcl_deliver {

    if (obj.hits > 0) {
           set resp.http.X-Cache = "HIT";
    } else {
           set resp.http.X-Cache = "MISS";
    }
 if (obj.hits > 0) {
           set resp.http.X-Cache-Lookup = "HIT";
    } else {
           set resp.http.X-Cache-Lookup = "MISS";
    }
 unset resp.http.X-Varnish;
 unset resp.http.Via;
 unset resp.http.Server;
 unset resp.http.X-Powered-By;
#return (deliver);

}

Best Answer

The problem here is very common: you're doing redirect http -> https at application level.

Apache/PHP don't know they are already running in SSL, as apache is running in http (then passing to varnish, then passing to nginx).

The solution is simple: your PHP Application needs to have the PHP ENV variable $_SERVER['HTTPS'] = "on".

You can do in different ways:

  • in httpd.conf using apache SetEnv,
  • in .htaccess using again SetEnv
  • in your php scripts.

Plus: I would also do a redirect from http to https at varnish level: add a custom header like X-Nginx = on when request come from your nginx. In varnish read that header, if it doesn't exist, then redirect the user to https URL.

NOTE: If you're using wordpress (as it seems in your vcl file) don't forget to add this:

define('FORCE_SSL_ADMIN', true);