angle-uparrow-clockwisearrow-counterclockwisearrow-down-uparrow-leftatcalendarcard-listchatcheckenvelopefolderhouseinfo-circlepencilpeoplepersonperson-fillperson-plusphoneplusquestion-circlesearchtagtrashx

Flask's SERVER_NAME, subdomains and 404 errors

Setting Flask's SERVER_NAME can give 404 errors if you are using subdomains.

25 November 2020 Updated 25 November 2020
In Flask
post main image
https://unsplash.com/@azhar93

This is a short post about Flask and the config variable SERVER_NAME. Like many developers I bumped into this at a certain moment, and I thought I share my story. Hopefully this will prevent headaches for some.

My websites must be available by typing the following addresses in the browser:

  • https://example.com = 'without-www', and,
  • https://www.example.com = 'with-www'

Whats more, for this website I had decided that the URLs in the generated pages must be 'without-www' or 'with-www', depending on what the request URL was. This means that if I type:

https://example.com/contact

there is no redirection and all links in the generated page will (appear to) start with https://example.com. Or, if I type:

https://www.example.com/contact

there is no redirection and all links in the generated page will (appear to) start with https://www.example.com.

I must add that this is not the best practice and that you always should use a single canonical name, meaning that your website is either 'with-www' or 'without-www', and never both! In a next post I will get into details about this.

Anyway, the above worked fine for all my websites except one, this website in fact. Months ago this was not a problem but it suddenly changed after a major code update. To prevent 404 errors for the 'with-www' version I solved this quickly by having Nginx redirect 'with-www' to 'without-www'. And then there were more urgent other projects and months passed.

Recently I was updating my (ISPConfig) server and thought I try this again. Of course still 404 errors for the 'with-www' version.

I made some time and decided to create the same situation on my development PC. This meant copying the Letsencrypt certificates to my PC and slighty modifying the development configuration by changing FLASK_ENV to 'production' and removing the 'with-www' to 'without-www' redirect in Nginx. I left FLASK_DEBUG=True so I could put breakpoints (exceptions) in the Python code and see what was going on.

Fortunately I experienced the same 404 errors when changing from 'without-www' to 'with-www' on my PC. Then, after some time I restarted Flask and now suddenly the 'with-www' version worked and the 'without-www' version gave 404 errors. What? The other way around?

Searching the internet for 'Flask 404' I bounced into pages, see links below, that hinted that the config variable SERVER_NAME could be the problem.

Then I remembered adding SERVER_NAME to my config when I was working on Celery tasks. I did this because I thought it could be useful to generate Jinja templates with url_for() links in some tasks.  Later I discarded this idea. Tasks should be should straightforward and not contain bells and whistles. But I did not remove the SERVER_NAME code, because everything was running fine, and after setting redirection in Nginx there were more urgent other projects, what else is new.

Description of what happened

My factory.py (__init__.py) looked like:

def create_app():
    ...
    @app.before_request
    def before_request():
        ....
        # get server_name from http_host
        if current_app.config.get('SERVER_NAME') is None:
            http_host = request.environ.get('HTTP_HOST')
            current_app.config['SERVER_NAME'] = http_host
        ....

Here I get SERVER_NAME from the request if it has not been set. This means once config[SERVER_NAME] is set it is not updated any more. If I start with:

https://www.peterspython.com

then the SERVER_NAME is set to: www.peterspython.com. But if I start with:

https://peterspython.com

then the SERVER_NAME is set to: peterspython.com.

In both cases config remembers the SERVER_NAME between requests (of this session) so if I change a working 'with-www' URL to a 'without-www' URL, then we get the 404 errors because Flask routing uses a wrong SERVER_NAME.

The behavior of Flask's SERVER_NAME is documented well but fully understanding it another thing. My problem is also described in the article 'Things You Should Know About Flask SERVER_NAME', see links below:

'Once you set SERVER_NAME, Flask can only serve request from one single domain and return 404 for other domains. If SERVER_NAME = mydomain.com, it won’t serve request from www.mydomain.com ...'

Solution

The solution is of course to remove the line:

        ....
        if current_app.config.get('SERVER_NAME') is None:
        ....

This will always set the SERVER_NAME config variable on every request. Or even better (?), remove these lines all together because I do not use SERVER_NAME anywhere.

Summary

At one point I added the config variable SERVER_NAME to my code and set this variable in before_request only when it was not already set. I forgot that my website could run with both 'with-www' and 'without-www' URLs and that the Flask config is persistent between requests of the same session. Once I got there it was easy to solve.

This would never have happened if I had chosen a single canonical name for my website. With this canonical name Flask would only have to deal with either 'with-www' or 'without-www' but never with both. In a next post I will explain why your canonical name should always be 'with-www'.

Anyway, lessons learned ... again.

Links / credits

SERVER_NAME configuration should not implicitly change routing behavior. #998
https://github.com/pallets/flask/issues/998

Things You Should Know About Flask SERVER_NAME
https://code.luasoftware.com/tutorials/flask/things-you-should-know-about-flask-server-name/

Unexplainable Flask 404 errors
https://stackoverflow.com/questions/24437248/unexplainable-flask-404-errors

Read more

Flask

Leave a comment

Comment anonymously or log in to comment.

Comments (2)

Leave a reply

Reply anonymously or log in to reply.

avatar

This has been clawing at me all afternoon. Thank you for sharing this.

avatar

A word of warning, changing the app config every before_request is sensitive to race conditions. So if you are using a threaded model of Flask execution, I would strongly advise against using the presented approach.
A small demo I tried to see if it would fit my needs in this gist: https://gist.github.com/eelkevdbos/14177eb9d72f5c96ed0f22ed64c30d19