title: How to proxy NGINX, Varnish, and PHP-FPM over SSL
body: <p>Trying to figure out how to host a WordPress site with NGINX and PHP-FPM, but also add Varnish for caching; and, to make it worse, have this offered over SSL.</p>
  
  <p>I've worked with NGINX, Varnish, and Gunicorn for an SSL Django site, and I thought this might be similar.  I've also setup Varnish, Apache and WordPress before.  But so far my logs have been fruitless.</p>
  
  <hr>
  
  <p>Here's <strong>what is working</strong> right now (NGINX and FPM):</p>
  
  <h3>NGINX port 80 forwarding to 443 (also forwarding www to non-www).</h3>
  
  <pre><code>server {
      listen 80;
      listen [::]:80;
      server_name example.com www.example.com
      return 301 https://example.com$request_uri;
  }
  </code></pre>
  
  <h3>NGINX port 443 passing to PHP-FPM listening on port 9000</h3>
  
  <pre><code>server {
      listen 443 ssl;
      listen [::]:443 ssl;
  
      access_log /var/log/nginx-access.log;
      error_log /var/log/nginx-error.log;
  
      server_name example.com;
  
      location / {
          try_files $uri $uri/ /index.php?q=$uri&amp;$args;
      }
  
      location ~ \.php$ {
          try_files $uri =404;
          fastcgi_split_path_info ^(.+\.php)(/.+)$;
          fastcgi_pass unix:/var/run/php5-fpm.sock;
          fastcgi_index index.php;
          include fastcgi_params;
          fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
      }
  }
  </code></pre>
  
  <hr>
  
  <h3>So I add Varnish</h3>
  
  <p>I've set up Varnish on port <code>6081</code> (default) and point it to <code>8080</code> as a backend.</p>
  
  <h3>Then I add another port for NGINX to proxy to FPM (like before)</h3>
  
  <p>I've then added this config for NGINX to handle port 8080, posted here in its entirety since it's so small (<strong>NOTE! THIS SECTION SEEMS TO BE THE PROBLEM</strong>):</p>
  
  <pre><code>server {
      listen 127.0.0.1:8080;
  
      access_log /var/log/nginx-access.log;
      error_log /var/log/php-fpm-error.log;
  
      root /var/www/example.com;
  
      index index.php;
      port_in_redirect off;
  
      location / {
          try_files $uri $uri/ /index.php?q=$uri&amp;$args;
      }
  
      location ~ \.php$ {
          try_files $uri =404;
          fastcgi_split_path_info ^(.+\.php)(/.+)$;
          include fastcgi_params;
          fastcgi_index index.php;
          fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
          fastcgi_pass unix:/var/run/php5-fpm.sock;
      }
  }
  </code></pre>
  
  <p>And then I've altered the SSL section of NGINX to <code>proxy_pass</code> to Varnish (like I've done in my other projects):</p>
  
  <pre><code>upstream varnish_server {
      server 127.0.0.1:6081 fail_timeout=0;
  }
  
  server {
      listen 443 ssl;
      listen [::]:443 ssl;
  
      # etc. 
  
      location / {
          proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
          proxy_set_header Host $http_host;
          proxy_redirect off;
          proxy_set_header 'Access-Control-Allow-Origin' '*';
  
          proxy_set_header X-Forwarded-Host $http_host;
          proxy_set_header X-Real-IP $remote_addr;
          proxy_set_header X-Forwarded-Proto https;
          proxy_set_header HTTPS "on";
          proxy_pass http://varnish_server;
          break;
      }
  }
  </code></pre>
  
  <h3>Errors I've received:</h3>
  
  <p>varnishlog says this:</p>
  
  <pre><code>   11 ReqStart     c 127.0.0.1 39742 1007025263
     11 RxRequest    c GET
     11 RxURL        c /
     11 RxProtocol   c HTTP/1.0
     11 RxHeader     c X-Forwarded-For: 12.34.567.890
     11 RxHeader     c Host: example.com
     11 RxHeader     c Access-Control-Allow-Origin: *
     11 RxHeader     c X-Forwarded-Host: example.com
     11 RxHeader     c X-Real-IP: 12.34.567.890
     11 RxHeader     c X-Forwarded-Proto: https
     11 RxHeader     c HTTPS: on
     11 VCL_call     c pass pass
     11 FetchError   c no backend connection
     11 VCL_call     c error deliver
     11 VCL_call     c deliver deliver
     11 TxProtocol   c HTTP/1.1
     11 TxStatus     c 503
     11 TxResponse   c Service Unavailable
     11 TxHeader     c Server: Varnish
  </code></pre>
  
  <p>nginx-error.log says this:</p>
  
  <pre><code>[error] 7532#0: *1 upstream timed out (110: Connection timed out) while reading response header from upstream, client: 12.34.567.890, server: example.com, request: "GET / HTTP/1.1", upstream: "http://127.0.0.1:6081/", host: "example.com"
  </code></pre>
  
  <p>Visually the site hangs for about a minute or two, times out; can't see any other errors.</p>
  
  <p>Also, note that I added this to wp-config.php, as recommended in <a href="https://codex.wordpress.org/Administration_Over_SSL" rel="nofollow noreferrer">WordPress Codex</a>:</p>
  
  <pre><code>if (strpos($_SERVER['HTTP_X_FORWARDED_PROTO'], 'https') !== false)
         $_SERVER['HTTPS']='on';
  </code></pre>
  
  <h2>Any help on this would be much appreciated! :D</h2>
  
  <h3>Update</h3>
  
  <p>Varnish default.vcl as requested; though, it is virtually identical to tutslpus tutorial on it (<a href="https://code.tutsplus.com/tutorials/optimizing-wordpress-with-varnish-and-w3-total-cache--cms-21136" rel="nofollow noreferrer">here</a>):</p>
  
  <pre><code>backend default {
      .host = "127.0.0.1";
      .port = "8080";
      .connect_timeout = 60s;
      .max_connections = 500;
  }
  
  acl purge {
      "127.0.0.1";
      "localhost";
  }
  
  sub vcl_recv {
      set req.grace = 2m;
  
      # Set X-Forwarded-For header for logging in nginx
      remove req.http.X-Forwarded-For;
      set    req.http.X-Forwarded-For = client.ip;
  
      # Remove has_js and CloudFlare/Google Analytics __* cookies and statcounter is_unique
      set req.http.Cookie = regsuball(req.http.Cookie, "(^|;\s*)(_[_a-z]+|has_js|is_unique)=[^;]*", "");
      # Remove a ";" prefix, if present.
      set req.http.Cookie = regsub(req.http.Cookie, "^;\s*", "");
  
      # Either the admin pages or the login
      if (req.url ~ "/wp-(login|admin|cron)") {
          # Don't cache, pass to backend
          return (pass);
      }
  
      # Remove the wp-settings-1 cookie
      set req.http.Cookie = regsuball(req.http.Cookie, "wp-settings-1=[^;]+(; )?", "");
  
      # Remove the wp-settings-time-1 cookie
      set req.http.Cookie = regsuball(req.http.Cookie, "wp-settings-time-1=[^;]+(; )?", "");
  
      # Remove the wp test cookie
      set req.http.Cookie = regsuball(req.http.Cookie, "wordpress_test_cookie=[^;]+(; )?", "");
  
      # Static content unique to the theme can be cached (so no user uploaded images)
      # The reason I don't take the wp-content/uploads is because of cache size on bigger blogs
      # that would fill up with all those files getting pushed into cache
      if (req.url ~ "wp-content/themes/" &amp;&amp; req.url ~ "\.(css|js|png|gif|jp(e)?g)") {
          unset req.http.cookie;
      }
  
      # Even if no cookies are present, I don't want my "uploads" to be cached due to their potential size
      if (req.url ~ "/wp-content/uploads/") {
          return (pass);
      }
  
      # any pages with captchas need to be excluded
      if (req.url ~ "^/contact/" || req.url ~ "^/links/domains-for-sale/") {
          return(pass);
      }
  
      # Check the cookies for wordpress-specific items
      if (req.http.Cookie ~ "wordpress_" || req.http.Cookie ~ "comment_") {
          # A wordpress specific cookie has been set
          return (pass);
      }
  
      # allow PURGE from localhost
      if (req.request == "PURGE") {
          if (!client.ip ~ purge) {
                  error 405 "Not allowed.";
          }
          return (lookup);
      }
  
      # Force lookup if the request is a no-cache request from the client
      if (req.http.Cache-Control ~ "no-cache") {
          return (pass);
      }
  
      # Try a cache-lookup
      return (lookup);
  
  }
  
  sub vcl_deliver {
      if (obj.hits &gt; 0) { # Add debug header to see if it's a HIT/MISS and the number of hits, disable when not needed
          set resp.http.X-Cache = "HIT";
      } else {
          set resp.http.X-Cache = "MISS";
      }
      # Please note that obj.hits behaviour changed in 4.0, now it counts per objecthead, not per object
      # and obj.hits may not be reset in some cases where bans are in use. See bug 1492 for details.
      # So take hits with a grain of salt
      set resp.http.X-Cache-Hits = obj.hits;
  }
  
  sub vcl_fetch {
      # set obj.grace = 5m;
      set beresp.grace = 2m;
  
      if (req.url !~ "wp-admin|wp-login|product|cart|checkout|my-account|/?remove_item=") {
          unset beresp.http.set-cookie;
      }
  }
  </code></pre>
  
  <p>And netstat, as requested:</p>
  
  <pre><code>root@server:/# netstat -tnlp
  Active Internet connections (only servers)
  Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
  tcp        0      0 127.0.0.1:11211         0.0.0.0:*               LISTEN      13454/memcached 
  tcp        0      0 127.0.0.1:8080          0.0.0.0:*               LISTEN      21420/nginx     
  tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      21420/nginx     
  tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      3583/sshd       
  tcp        0      0 127.0.0.1:25            0.0.0.0:*               LISTEN      3778/master     
  tcp        0      0 0.0.0.0:443             0.0.0.0:*               LISTEN      21420/nginx     
  tcp        0      0 0.0.0.0:6081            0.0.0.0:*               LISTEN      29149/varnishd  
  tcp        0      0 127.0.0.1:6082          0.0.0.0:*               LISTEN      29148/varnishd  
  tcp6       0      0 :::3306                 :::*                    LISTEN      3643/mysqld     
  tcp6       0      0 :::80                   :::*                    LISTEN      21420/nginx     
  tcp6       0      0 :::22                   :::*                    LISTEN      3583/sshd       
  tcp6       0      0 ::1:25                  :::*                    LISTEN      3778/master     
  tcp6       0      0 :::443                  :::*                    LISTEN      21420/nginx     
  tcp6       0      0 :::6081                 :::*                    LISTEN      29149/varnishd  
  tcp6       0      0 ::1:6082                :::*                    LISTEN      29148/varnishd
  </code></pre>