Kyeno
Kyeno

Reputation: 599

Nginx reverse proxy to Wordpress on an URI

I have a Symfony 2.5.X app running on an nginx server. I will call it domain.com.

The /news URI within that server is configured as a reverse proxy to a remote machine, where I run Wordpress blog on nginx server again. I will call it blog.domain.com.

domain.com's configuration looks like that:

server {
  listen 80;
  server_name domain.com;

  set $project_path /home/webserver/prod.domain.com;
  root $project_path/web;

  error_log /home/webserver/prod.domain.com/app/logs/nginx_error.log;
  access_log /home/webserver/prod.domain.com/app/logs/nginx_access.log;

  charset utf-8;
  client_max_body_size 65m;

  # Some extra speed
  open_file_cache max=1000 inactive=20s;
  open_file_cache_valid 30s;
  open_file_cache_min_uses 2;
  open_file_cache_errors on;

  # Reverse-proxy all /news calls to remote machine
  location ~ /news?(.*) {

    access_log off;

    proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
    proxy_set_header        Host                    blog.domain.com;  # without it it doesn't work
    #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       http;
    proxy_set_header        X-Custom-Secret         6ffe3dba7213c678324a101827aa3cf22c;

    proxy_redirect          off;
    proxy_buffering         off;
    #proxy_intercept_errors  on;

    proxy_pass http://blog.domain.com:80;
    break;
  }
  # Default URLs
  location / {
    try_files $uri /app.php$is_args$args;
  }

  # Error pages (static)
  #error_page 403                 /errorpages/403.html;
  error_page 404                  /errorpages/404.html;
  #error_page 405                 /errorpages/405.html;
  error_page 500 501 502 503 504  /errorpages/5xx.html;

  # Don't log garbage, add some browser caching
  location ~* ^.+\.(ogg|ogv|svg|svgz|eot|otf|woff|mp4|ttf|rss|atom|jpg|jpeg|gif|png|ico|zip|tgz|gz|rar|bz2|doc|xls|exe|ppt|tar|mid|midi|wav|bmp|rtf)$ {
    access_log off;
    log_not_found off;
    expires max;
    add_header Pragma "public";
    add_header Cache-Control "public, must-revalidate, proxy-revalidate";
    try_files $uri /app.php?$query_string;
  }
  location ~* ^.+\.(css|js)$ {
    expires modified +1m;
    add_header Pragma "private";
    add_header Cache-Control "private";
    etag on;
    try_files $uri /app.php?$query_string;
  }
  location = /robots.txt {
    allow all;
    access_log off;
    log_not_found off;
  }

  # Disallow .htaccess, .htpasswd and .git
  location ~ /\.(ht|git) {
    deny all;
  }

  # Parse PHP
  location ~ ^/(app|app_dev|config)\.php(/|$) {
    include                 fastcgi_params;
    fastcgi_split_path_info ^(.+\.php)(/.*)$;
    fastcgi_param           SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_param           HTTPS           off;
    fastcgi_pass            php;
  }
}

blog.domain.com's configuration looks like that:

server {
  listen 80;
  server_name blog.domain.com;

  root  /home/webserver-blog/news;

  access_log    /home/webserver-blog/logs/http_access.log;
  error_log     /home/webserver-blog/logs/http_error.log;

  charset utf-8;
  client_max_body_size 65m;

  # Some extra speed
  open_file_cache max=1000 inactive=20s;
  open_file_cache_valid 30s;
  open_file_cache_min_uses 2;
  open_file_cache_errors on;

  # Default URLs
  location / {
    # This never gets parsed as / is reserved for our main server
  }
  location ~* ^/news/(wp-content|wp-admin) {  # without this directive I didn't have any static files
    root /home/webserver-topblog/;
  }
  location ~* ^/news {
    try_files $uri $uri/ /index.php?args;
  }

  # Don't log garbage
  location ~* ^.+\.(ogg|ogv|svg|svgz|eot|otf|woff|mp4|ttf|rss|atom|jpg|jpeg|gif|png|ico|zip|tgz|gz|rar|bz2|doc|xls|exe|ppt|tar|mid|midi|wav|bmp|rtf)$ {
    access_log off;
    log_not_found off;
    expires max;
  }
  location = /robots.txt {
    allow all;
    access_log off;
    log_not_found off;
  }

  # Disallow .htaccess or .htpasswd
  location ~ /\.ht {
    deny all;
  }

  # Disallow logs
  location ~ ^/logs/.*\.(log|txt)$ {
    deny all;
  }

  # Parse PHP
  location ~ \.php$ {
      #if (!-e $request_filename) { rewrite / /index.php last; }
      try_files $uri =404;

      include        fastcgi_params;
      fastcgi_index  index.php;
      fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
      fastcgi_pass   php;
  }
}

As you can figure out, my Wordpress resides in /home/webserver-blog/news/. I have a slightly modified index.php file in Wordpress that checks for X-Custom-Secret header, and if it's not present (or invalid), it forces a 301 redirection to domain.com/news/

Now I have tried several different approaches to get it running properly.

I am completely out of ideas... Any help would be much appreciated.

Upvotes: 1

Views: 2278

Answers (1)

Kyeno
Kyeno

Reputation: 599

I think I managed to come with a so-so solution. Far from being perfect or clean, but... well, it works.

blog.domain.com's config:

server {
  listen 80;
  server_name blog.domain.com;

  root  /home/webserver-blog;

  access_log    /home/webserver-blog/logs/http_access.log;
  error_log     /home/webserver-blog/logs/http_error.log;

  charset utf-8;
  client_max_body_size 65m;

  # Some extra speed
  open_file_cache max=1000 inactive=20s;
  open_file_cache_valid 30s;
  open_file_cache_min_uses 2;
  open_file_cache_errors on;

  # Default URLs
  location ~* ^/news$ {
    rewrite ^ $scheme://domain.com/news/ permanent;   # ** HARDCODED production url
    break;
  }
  location / {
    try_files $uri $uri/ @redir;
  }
  location @redir {
    rewrite ^/news/(.*)$ /news/index.php?$1 last;
  }

  # Don't log garbage
  location ~* ^.+\.(ogg|ogv|svg|svgz|eot|otf|woff|mp4|ttf|rss|atom|jpg|jpeg|gif|png|ico|zip|tgz|gz|rar|bz2|doc|xls|exe|ppt|tar|mid|midi|wav|bmp|rtf)$ {
    access_log off;
    log_not_found off;
    expires max;
  }
  location = /robots.txt {
    allow all;
    access_log off;
    log_not_found off;
  }

  # Disallow .htaccess or .htpasswd
  location ~ /\.ht {
    deny all;
  }

  # Disallow logs
  location ~ ^/logs/.*\.(log|txt)$ {
    deny all;
  }

  # Parse PHP
  location ~ \.php$ {
    include                 fastcgi_params;
    fastcgi_split_path_info ^(.+\.php)(/.*)$;
    fastcgi_param           SCRIPT_FILENAME  $document_root$fastcgi_script_name;
    fastcgi_param           HTTPS           off;
    fastcgi_pass            php;
  }
}

So the trick is I'm still operating on filesystem directories and not fancy all-the-way-around rewrites and redirects. news/ still remains a physical directory in the filesystem that gets read with the location / directive from nginx. Previous issues with exposing the blog.domain.com domain on trying to access without slashes seem to be native nginx's behaviour - it sees a directory, it adds a slash at the end; and since it's server_name is set to blog.domain.com, here we go. Hardcoding production URL and putting that rule on top pretty much fixed the problem.

@redir location again enabled the Wordpress' permalinks nicely.

One more thing I have added to entire setup to prevent people form going directly on http://blog.domain.com/ is another index.php file stored directly in /home/webserver-blog/:

<?php
/*
 * domain.com redirector
 */
$production = 'http://domain.com/news/';

// Redirect nicely
if(isset($_SERVER['REQUEST_URI']) and $_SERVER['REQUEST_URI'] !== '/') {

  $target = sprintf('%s%s', $production, preg_replace('/^\//', null, $_SERVER['REQUEST_URI']));
  header('Location: ' . $target);
}
else header('Location: ' . $production);

...and, as mentioned before, few lines on top of Wordpress' original index.php:

<?php
/*
 *  wordpress loader
 */
$production = 'http://domain.com/news/';

// Allow only reverse-proxied requests
if(!isset($_SERVER['HTTP_X_CUSTOM_SECRET']) or $_SERVER['HTTP_X_CUSTOM_SECRET'] !== md5('your-md5encoded-text-in-proxy_set_header-X-Custom-Secret')) {
  die(header('Location: ' . $production));
}
require_once dirname(__FILE__) . '/index-wp-org.php';

Ugly... but works. I'd still be happy to hear nicer solutons. :)

Upvotes: 1

Related Questions