skip to Main Content

A while ago, I created a simple internal short URL service (with Django).
If an entry exists, for example, /abc (or whatever), it redirects to the stored, longer URL with a HTTP-Status of 301. So far, so good…

However, now I have the requirement to set up this service for the main website.

However, there is already a CMS running with nginx try_files and proxy_pass configured.

Nginx:

location / {
    try_files /maintenance.html $uri @cms;
}

location @cms {
    rewrite ^/(.*)/$     /$1    permanent;
    rewrite              ^(.*)$ /VirtualHostBase/$scheme/$host:$server_port/cms/VirtualHostRoot$1 break;
    proxy_pass           http://example_cms;

    include              /etc/nginx/cfg/proxy.conf;
    include              /etc/nginx/cfg/security.conf;
    gzip_static          on; # to serve pre-gzipped version
}

Can these be combined?
So, the desired flow is as follows:

  • if /abc found and returned in the short URL service, redirect with a HTTP Status from URL-Service;
  • otherwise, use the CMS and display a URL from the CMS using try_files + rewrite + proxy_pass.
  • If none of these apply, show a 404, which can also be served by the CMS.

2

Answers


  1. You want to prioritize the short URL service by checking its route first and, if no match is found, then proceeding to handle the request with the CMS as usual.

                   +-----> Django Short URL Service -----> 301 Redirect
                  |
    Request ----> |                                  
                  |                                  
                  +-----> CMS (via Nginx) -----------> Content/404
    

    You would need to modify the Nginx configuration by adding a new location block specifically for handling the paths that might match your short URLs. The Django application must be set up to serve on a specific endpoint or port, which can then be proxied by Nginx.

    As Dmitry notes in the comments, a try_files directive in Nginx does not support specifying more than one named location as a fallback.
    The last parameter of the try_files directive can indeed point to a named location or a URI, but only one such fallback is permitted.

    You can instead try to first attempt to serve the request as a static file, or via the short URL service, and use a single named location as the final fallback, which will then decide whether to serve the content from the CMS or return a 404.
    You would route the request internally within Nginx based on the presence or absence of the requested resource in the short URL service.

    • A primary location block will check for static files or redirect to the short URL service directly.
    • As the final fallback in try_files, decide if the request should be served by the CMS or if a 404 should be returned.
    http {
        # Other HTTP settings
    
        upstream django_short_url {
            server localhost:8000; # Django short URL service
        }
    
        server {
            # Other server settings
    
            location / {
                try_files $uri $uri/ @check_short_url;
            }
    
            location @check_short_url {
                # Attempt to proxy the request to the Django short URL service.
                # Django should return a specific status code or header if the URL is not found,
                # which Nginx can then use to decide whether to continue to the CMS.
                proxy_pass http://django_short_url;
                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_set_header X-Forwarded-Proto $scheme;
    
                # If the Django app determines that the URL does not match a short URL,
                # it could return a custom header or specific status that Nginx checks
                # to then forward the request to @cms using error_page or another mechanism.
            }
    
            location @cms {
                rewrite ^/(.*)/$ /$1 permanent;
                rewrite ^(.*)$ /VirtualHostBase/$scheme/$host:$server_port/cms/VirtualHostRoot$1 break;
                proxy_pass http://example_cms;
    
                include /etc/nginx/cfg/proxy.conf;
                include /etc/nginx/cfg/security.conf;
                gzip_static on; # to serve pre-gzipped version
            }
        }
    }
    

    The @check_short_url location is a named location which attempts to proxy every request to the Django short URL service. Depending on your Django app’s behavior, if a short URL does not exist, the app should either redirect (for valid short URLs) or signal back to Nginx (e.g., via a custom header or specific status code) that the request should be passed to the CMS.
    You might need to employ additional Nginx features like error_page directives within the @check_short_url location to internally redirect requests to @cms based on the response from the Django app.


    The "Using NGINX and NGINX Plus as an Application Gateway with uWSGI and Django" guide focuses on Nginx’s ability to serve as an application gateway in front of a Django application running via uWSGI, which is not your case.

    Login or Signup to reply.
  2. One possible solution is to intercept the 404 error from Short URL Service (@sus) and pass the request to CMS (@cms), which is configured as a error_page. A bit hacky, but it works.

    Example

    server {
        listen 8000;
    
        location / {
            # 1. At first, we pass the request to the Short URL Service (@sus)
            try_files $uri @sus;
        }
    
        location @sus {
            proxy_pass http://localhost:9001;
            # 2. Here we intercept all errors. We are interested in 404.
            proxy_intercept_errors on;
            # 3. Here we configure a error handler for 404:
            # pass request to the named location, respond with the code
            # returned by the proxy (=)
            error_page 404 = @cms;
        }
    
        location @cms {
            proxy_pass http://localhost:9002;
        }
    
    }
    
    
    # This is a mock server for @sus
    server {
        listen 9001;
    
        location = /short-url {
            return 301 /long-url;
        }
    
        location = /long-url {
            return 200 'response from sus: long-url';
        }
    
    }
    
    # This is a mock server for @cms
    server {
        listen 9002;
    
        location = /foo {
            return 200 'response from cms: foo';
        }
    
        location = /bar {
            return 200 'response from cms: bar';
        }
    
        location = /short-url {
            return 200 'should not reach here';
        }
    }
    

    Demo

    $ curl -L http://localhost:8000/short-url
    response from sus: long-url
    
    $ curl -L http://localhost:8000/foo
    response from cms: foo
    
    $ curl -L http://localhost:8000/bar
    response from cms: bar
    
    $ curl -L http://localhost:8000/not-found
    <html>
    <head><title>404 Not Found</title></head>
    <body>
    <center><h1>404 Not Found</h1></center>
    <hr><center>openresty</center>
    </body>
    </html>
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search