Home > OS >  Rewriting all URLs to a single PHP controller on nginx
Rewriting all URLs to a single PHP controller on nginx

Time:12-10

I've a web server which has two PHP files, index.php and controller.php, the latter which handles all non-/ requests with a p (for page) paramater, e.g.

/controller.php?p=some_page

The following nginx configuration works nicely:

server {
    listen 80;
    index index.php index.html;
    server_name localhost;
    error_log  /var/log/nginx/error.log;
    access_log /var/log/nginx/access.log;
    root /code;
    
    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_split_path_info ^(. \.php)(/. )$;
        fastcgi_pass php:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
    }
}

However, I now want to clean up the URLs, and want / to go to index.php and /some_page to go to /controller.php?p=some_page.

Here's the new configuration I'm trying:

server {
    listen 80;
    index index.php index.html;
    server_name localhost;
    error_log  /var/log/nginx/error.log;
    access_log /var/log/nginx/access.log;
    root /code;

    # this is new, and makes index.php handle /
    location / {
        index index.php;
        try_files $uri $uri/ =404;
    }
    
    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_split_path_info ^(. \.php)(/. )$;
        fastcgi_pass php:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
    }

    # this is new, but doesn't work.
    location @rewrites {
        if ($uri ~* ^/([0-9a-z_] )$) {
            set $page_to_view "/controller.php?p=$1";
            rewrite ^/([0-9a-z_] )$ $scheme://$http_host/controller.php?p=$1 last;
        }
    }
}

The browser address bar shows this:

- http://localhost:8080/some_page                  (initial request)
- http://localhost:8080/controller.php?p=some_page (first redirect one second later)
- https://localhost/some_page/                      (final redirect another second later)

So, it ends up on a URL which doesn't have the original port, with a trailing slash, and using scheme https.

What can I do to fix it?

PS It would be a bonus if trailing slashes didn't matter (i.e. localhost:8080/some_page and localhost:8080/some_page/ shows the same thing.

PS the port 8080 is just me locally testing via Docker, which maps container 80 to host 8080.

Update:

I've implemented Richard's answers, and tried the suggested curl:

$ curl -I http://localhost:8080/some_page
HTTP/1.1 301 Moved Permanently
Server: nginx/1.21.4
Date: Thu, 09 Dec 2021 15:07:11 GMT
Content-Type: text/html
Content-Length: 169
Location: http://localhost/some_page/
Connection: keep-alive

Note the lack of port 8080.

Update 2:

With nginx directive absolute_redirect off;, I get this:


$ curl -I http://localhost:8080/some_page
HTTP/1.1 301 Moved Permanently
Server: nginx/1.21.4
Date: Thu, 09 Dec 2021 16:01:31 GMT
Content-Type: text/html
Content-Length: 169
Connection: keep-alive
Location: /some_page/

$ curl -I http://localhost:8080/some_page/
HTTP/1.1 404 Not Found
Server: nginx/1.21.4
Date: Thu, 09 Dec 2021 16:01:34 GMT
Content-Type: text/html
Content-Length: 153
Connection: keep-alive

So, the question remains how a request to /some_page/ can serve up the response from /controller.php?p=some_page

CodePudding user response:

A named location (e.g. location @rewrites) is usually invoked from the last element of a try_files statement.

For example:

location / {
    index index.php;
    try_files $uri $uri/ @rewrites;
}

In your location @rewrites block, the if statement is unnecessary, the set variable is unused, and the rewrite statement will provoke an external redirect.

Try:

location @rewrites {
    rewrite ^/([0-9a-z_] )$ /controller.php?p=$1 last;
}

CodePudding user response:

I've landed on a configuration which works with the original links, but also the pretty URLs I was aiming for.

server {
    listen 80;
    index index.php;
    server_name localhost;
    error_log  /var/log/nginx/error.log;
    access_log /var/log/nginx/access.log;
    root /code;

    location = / {
        index index.php;
        try_files $uri $uri/ =404;
    }

    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_split_path_info ^(. \.php)(/. )$;
        fastcgi_pass php:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
    }

    location ~* {
        rewrite ^/([0-9a-z_] )/?$ /controller.php?p=$1 last;
    }
}

The key seems to be to not use a named location for the rewrite, but just match "everything else" with location ~* if the request isn't for / or a PHP file.

  • Related