0

So, I have a wordpress website running behind nginx -> varnish -> httpd

The htaccess rule for wp-login.php is set as:

<Files wp-login.php> allow from client ip deny from all </Files> 

This used to work fine without using varnish, but when I put varnish in the middle between nginx and httpd, this caching issue or IP forwarding issue started happening.

Since I can easily change the webserver config, I can disable/enable varnish for a site easily. So, as a test, I changed the htaccess rule to:

<Files wp-login.php> allow from Server Public IP deny from all </Files> 

This made wp-login accessible to everyone. Then I disabled varnish and kept the Server public IP in htaccess, and now nobody can access the page (which is what is supposed to happen).

So, the culprit is varnish.

I have mod_cloudflare setup on apache. I've also tested by switching it to mod_remoteip to no avail.

Here's my nginx:443, varnish:82 and apache:8181 vhost templates (This IP 108.148.54.124 is an example for the server public IP):

server { listen 108.148.54.124:443 ssl http2; server_name %domain_idn% %alias_idn%; access_log /usr/local/apache/domlogs/%domain%.bytes bytes; access_log /usr/local/apache/domlogs/%domain%.log full; error_log /usr/local/apache/domlogs/%domain%.error.log error; ssl_certificate %ssl_cert_path%/%domain%.bundle; ssl_certificate_key %ssl_key_path%/%domain%.key; ssl_protocols TLSv1.3; ssl_ciphers EECDH+ECDSA+AESGCM:EECDH+aRSA+AESGCM:EECDH+ECDSA+SHA384:EECDH+ECDSA+SHA256:EECDH+aRSA+SHA384:EECDH+aRSA+SHA256:EECDH+aRSA!RC4:EECDH:!RC4:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!PSK:!SRP:!DSS; ssl_prefer_server_ciphers on; ssl_session_cache shared:SSL:10m; ssl_session_timeout 60m; location / { location ~.*\.(3gp|gif|jpg|jpeg|png|ico|wmv|avi|asf|asx|mpg|mpeg|mp4|pls|mp3|mid|wav|swf|flv|html|htm|txt|js|css|exe|zip|tar|rar|gz|tgz|bz2|uha|7z|doc|docx|xls|xlsx|pdf|iso|woff|ttf|svg|eot|sh|webp)$ { root %docroot%; expires max; try_files $uri $uri/ @backend; } error_page 405 = @backend; error_page 500 = @custom; add_header X-Cache "HIT from Backend"; add_header Strict-Transport-Security "max-age=31536000"; add_header X-XSS-Protection "1; mode=block" always; add_header X-Content-Type-Options "nosniff" always; proxy_pass %proxy_protocol%://108.148.54.124:82; include proxy.inc; } location @backend { internal; proxy_pass %proxy_protocol%://108.148.54.124:82; include proxy.inc; } location @custom { internal; proxy_pass %proxy_protocol%://108.148.54.124:82; include proxy.inc; } location ~ .*\.(php|jsp|cgi|pl|py)?$ { proxy_pass %proxy_protocol%://108.148.54.124:82; include proxy.inc; } location ~ /\.ht {deny all;} location ~ /\.svn/ {deny all;} location ~ /\.git/ {deny all;} location ~ /\.hg/ {deny all;} location ~ /\.bzr/ {deny all;} location ~\.(ini|log|conf)$ {deny all;error_page 403 =404 / ;} disable_symlinks if_not_owner from=%docroot%; location /.well-known/acme-challenge { default_type "text/plain"; alias /usr/local/apache/autossl_tmp/.well-known/acme-challenge; } location /.well-known/pki-validation { default_type "text/plain"; alias /usr/local/apache/autossl_tmp/.well-known/acme-challenge; } } ..... 
backend %backend_domain% { .host = "108.148.54.124"; .port = "8181"; } sub vcl_recv { if (req.http.host ~ "%domain%") { set req.backend_hint = %backend_domain%; # Always cache the following file types for all users. if (req.url ~ "(?i)\.(png|gif|jpeg|jpg|ico|swf|css|js|html|htm)(\?[a-z0-9]+)?$") { unset req.http.Cookie; } # Remove any Google Analytics based cookies set req.http.Cookie = regsuball(req.http.Cookie, "has_js=[^;]+(; )?", ""); set req.http.Cookie = regsuball(req.http.Cookie, "__utm.=[^;]+(; )?", ""); set req.http.Cookie = regsuball(req.http.Cookie, "_ga=[^;]+(; )?", ""); set req.http.Cookie = regsuball(req.http.Cookie, "utmctr=[^;]+(; )?", ""); set req.http.Cookie = regsuball(req.http.Cookie, "utmcmd.=[^;]+(; )?", ""); set req.http.Cookie = regsuball(req.http.Cookie, "utmccn.=[^;]+(; )?", ""); # Do not cache AJAX requests. if (req.http.X-Requested-With == "XMLHttpRequest") { return(pass); } # Post requests will not be cached if (req.http.Authorization || req.method == "POST") { return (pass); } if (req.method != "GET" && req.method != "HEAD") { return (pass); } # Do not cache Authorized requests. if (req.http.Authorization) { return(pass); } # LetsEncrypt Certbot passthrough if (req.url ~ "^/\.well-known/acme-challenge/") { return (pass); } if (req.url ~ "^/\.well-known/pki-validation/") { return (pass); } # Forward client's IP to the backend if (req.restarts == 0) { if (req.http.X-Real-IP) { set req.http.X-Forwarded-For = req.http.X-Real-IP; } else if (req.http.X-Forwarded-For) { set req.http.X-Forwarded-For = req.http.X-Forwarded-For + ", " + client.ip; } else { set req.http.X-Forwarded-For = client.ip; } } ### Wordpress ### if (req.url ~ "(wp-admin|post\.php|edit\.php|wp-login)") { return(pass); } if (req.url ~ "/wp-cron.php" || req.url ~ "preview=true") { return (pass); } # WP-Affiliate if ( req.url ~ "\?ref=" ) { return (pass); } set req.http.Cookie = regsuball(req.http.Cookie, "wp-settings-1=[^;]+(; )?", ""); set req.http.Cookie = regsuball(req.http.Cookie, "wp-settings-time-1=[^;]+(; )?", ""); set req.http.Cookie = regsuball(req.http.Cookie, "wordpress_test_cookie=[^;]+(; )?", ""); set req.http.Cookie = regsuball(req.http.Cookie, "PHPSESSID=[^;]+(; )?", ""); return (hash); } } 
<VirtualHost 108.148.54.124:8443> ServerName %domain_idn% %domain_aliases% ServerAdmin webmaster@%domain% DocumentRoot %docroot% UseCanonicalName Off ScriptAlias /cgi-bin/ %docroot%/cgi-bin/ CustomLog /usr/local/apache/domlogs/%domain%.bytes bytes CustomLog /usr/local/apache/domlogs/%domain%.log combined ErrorLog /usr/local/apache/domlogs/%domain%.error.log ## Custom settings are loaded below this line (if any exist) # IncludeOptional "/usr/local/apache/conf/userdata/%user%/%domain%/*.conf" SSLEngine on SSLCertificateFile %ssl_cert_path%/%domain%.cert SSLCertificateKeyFile %ssl_key_path%/%domain%.key SSLCertificateChainFile %ssl_cert_path%/%domain%.bundle SetEnvIf User-Agent ".*MSIE.*" nokeepalive ssl-unclean-shutdown <IfModule mod_userdir.c> UserDir disabled UserDir enabled %user% </IfModule> <IfModule mod_suexec.c> SuexecUserGroup %user% %group% </IfModule> <IfModule mod_suphp.c> suPHP_UserGroup %user% %group% suPHP_ConfigPath %home%/%user% </IfModule> <IfModule mod_ruid2.c> RMode config RUidGid %user% %group% </IfModule> <IfModule itk.c> AssignUserID %user% %group% </IfModule> <Directory "%docroot%"> AllowOverride All SSLRequireSSL Require all granted </Directory> <IfModule proxy_fcgi_module> <FilesMatch \.php$> SetHandler "proxy:%backend_fcgi%|fcgi://localhost" </FilesMatch> </IfModule> </VirtualHost> 

and this is the main nginx conf file:

user nobody; worker_processes auto; #worker_rlimit_nofile 65535; error_log /var/log/nginx/error.log crit; pid /var/run/nginx.pid; events { worker_connections 1024; use epoll; multi_accept on; } http { sendfile on; tcp_nopush on; tcp_nodelay on; client_header_timeout 3m; client_body_timeout 3m; client_max_body_size 256m; client_header_buffer_size 4k; client_body_buffer_size 256k; large_client_header_buffers 4 32k; send_timeout 3m; keepalive_timeout 60 60; reset_timedout_connection on; server_names_hash_max_size 1024; server_names_hash_bucket_size 1024; ignore_invalid_headers on; connection_pool_size 256; request_pool_size 4k; output_buffers 4 32k; postpone_output 1460; include mime.types; default_type application/octet-stream; # Compression gzip gzip on; gzip_vary on; gzip_disable "MSIE [1-6]\."; gzip_proxied any; gzip_min_length 512; gzip_comp_level 6; gzip_buffers 8 64k; gzip_types text/plain text/xml text/css text/js application/x-javascript application/xml image/png image/x-icon image/gif image/jpeg image/svg+xml application/xml+rss text/javascript application/atom+xml application/javascript application/json application/x-font-ttf font/opentype; # Proxy settings proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass_header Set-Cookie; proxy_connect_timeout 300; proxy_send_timeout 300; proxy_read_timeout 300; proxy_buffers 32 4k; proxy_cache_path /var/cache/nginx levels=2 keys_zone=cache:10m inactive=60m max_size=512m; proxy_cache_key "$host$request_uri $cookie_user"; proxy_temp_path /var/cache/nginx/temp; proxy_ignore_headers Expires Cache-Control; proxy_cache_use_stale error timeout invalid_header http_502; proxy_cache_valid any 1d; open_file_cache_valid 120s; open_file_cache_min_uses 2; open_file_cache_errors off; open_file_cache max=5000 inactive=30s; open_log_file_cache max=1024 inactive=30s min_uses=2; # SSL Settings ssl_session_cache shared:SSL:10m; ssl_protocols TLSv1.3; ssl_prefer_server_ciphers on; ssl_ciphers "EECDH+ECDSA+AESGCM:EECDH+aRSA+AESGCM:EECDH+ECDSA+SHA384:EECDH+ECDSA+SHA256:EECDH+aRSA+SHA384:EECDH+aRSA+SHA256:EECDH+aRSA!RC4:EECDH:!RC4:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!PSK:!SRP:!DSS"; # Logs log_format main '$remote_addr - $remote_user [$time_local] $request ' '"$status" $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; log_format full '[$time_local] $remote_addr $remote_user - "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent" "$http_x_forwarded_for"'; log_format bytes '$body_bytes_sent'; #access_log /var/log/nginx/access.log main; access_log off; # Cache bypass map $http_cookie $no_cache { default 0; ~SESS 1; ~wordpress_logged_in 1; } # Include additional configuration include /etc/nginx/cloudflare.inc; include /etc/nginx/conf.d/*.conf; } 
1
  • The server-status for apache shows only Server IP as client whether varnish is enabled or not. Commented Jan 13, 2022 at 22:11

2 Answers 2

0

I just found a solution, which is to use this instead

<Files wp-login.php> SetEnvIf X-Forwarded-For %Client_IP% allow_me Allow from env=allow_me deny from all </Files> 

I can also put the SetEnvIf outside, i.e. at the top of the htaccess file, and just change the ip on this line every time my IP changes.

SetEnvIf X-Forwarded-For %Client_IP% allow_me <Files wp-login.php> Allow from env=allow_me deny from all </Files> 
0

The X-Forward-For header should contain the client IP address. This header will be set by Nginx and will also be used by Varnish.

It is possible that the X-Forward-For header looks like this:

X-Forwarded-For: 1.2.3.4, 5.6.7.8 

It's a matter of extracting the first value and matching that in your .htaccess file.

This article provides an easy way to perform allow from calls based on the value of the X-Forwarded-For header: Apache, use X-Forwarded-For for allow

This could translate into the following configuration:

<Files wp-login.php> SetEnvIf X-Forwarded-For ^1\.2\.3\.4 proxy_env Order allow,deny Satisfy Any Allow from env=proxy_env Deny from all </Files> 

You can also restrict access to wp-login.php in your Nginx configuration or in your Varnish VCL.

1
  • Thanks, but this almost repeats the same answer I wrote above... The only thing I didn't mention in the question is why I was trying to "allow from SERVER IP" in the first place. It was because mod_security was blocking some requests made by the server, so I whitelisted the server, which caused that everyone was whitelisted from mod_security, which led me into the path that whitelisting while having varnish infront of apache will be trouble. Commented Jan 15, 2022 at 10:43

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.