Score:1

nginx reverse proxy with docker containers and specific nested locations

sy flag

I would like to set up nginx as a reverse proxy with multiple apps on docker containers permanently residing under specific locations, e.g.:

https://sub.example.com/wiki
https://sub.example.com/app1
https://sub.example.com/app2

In particular, I want to use the mediawiki docker image next to other apps. This is my docker-compose.yml:

version: '3.5'
services:

  mediawiki:
    image: mediawiki
    restart: unless-stopped
    hostname: mediawiki
    ports:
      - "8080:80"
    links:
      - database
    volumes:
      - images:/var/www/html/images
      # - ./wiki/LocalSettings.php:/var/www/html/LocalSettings.php
    networks:
      - wiki
  
  database:
    image: mariadb
    restart: unless-stopped
    hostname: database
    environment:
      MYSQL_DATABASE: my_wiki
      MYSQL_USER: wikiuser
      MYSQL_PASSWORD: example
      MYSQL_RANDOM_ROOT_PASSWORD: 'yes'
    volumes:
      - db:/var/lib/mysql
    networks:
      - wiki

  app1:
    # ...
    expose:
      - "4000"
    networks:
      - apps

  nginx:
    image: nginx:1.23-alpine
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/conf.d:/etc/nginx/conf.d
      - ./certbot/conf:/etc/nginx/ssl
      - ./certbot/data:/usr/share/nginx/html/letsencrypt
    depends_on:
      - app1
      - mediawiki
    networks:
      - apps
      - wiki

  certbot:
    image: certbot/certbot:latest
    # ...
    volumes:
      - ./certbot/conf:/etc/letsencrypt
      - ./certbot/logs:/var/log/letsencrypt
      - ./certbot/data:/usr/share/nginx/html/letsencrypt
    networks:
      - apps
      - wiki

networks:
  apps:
  wiki:

The problem I am facing is that with the following default.conf, I am able to proxy the mediawiki container as well as the other apps, but certain links and resources return 404.

upstream testwiki {
    server mediawiki:80;
}

server {
    listen 80;
    listen [::]:80;

    server_name sub.example.com;

    location / {
        return 301 https://$server_name$request_uri;
    }
}

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    
    server_name sub.example.com;

    ssl_certificate /etc/nginx/ssl/live/sub.example.com/fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/live/sub.example.com/privkey.pem;

    location /wiki/ {
        proxy_pass http://testwiki/;
        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-Host $server_name;
    }
}

I suspect the reason for this behavior is due to relational URLs, such as <a href="/mw-config/index.php">complete the installation</a>, directing requests to the root location instead of the nested one. I have tried a lot of things, such as rewrite (incl. regex), sub_filter, proxy_redirect and proxy_set_header methods, but the best I have come up with is:

location /wiki/ {
    proxy_pass http://mediawiki:80/;
    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-Host $server_name;
}

location /app1/ {
    proxy_pass http://app1:4000/;
    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-Host $server_name;
}


if ($http_referer = "https://sub.example.com/wiki/") {
    set $proxypass http://mediawiki:80;
}
if ($http_referer = "https://sub.example.com/app1/") {
    set $proxypass http://app1:4000;
}
    
location / {
    proxy_pass $proxypass;
    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;
}

Since I am not able to configure a baseurl for the mediawiki (yet), I tried to proxy incoming requests at the root location depending on the http_referer. This works for all inital GET requests made at https://sub.example.com/wiki/ and also for the link mentioned above.

However, after clicking <a href="/mw-config/index.php">..., index.php makes further requests - again directed to https://sub.example.com/. Since the URL is not rewritten and the referer says https://sub.example.com/mw-config/index.php, these requests return 500.

My question is: How can I fix this behaviour to make my apps permanently reside at their respective location? I can unfortunately not change the subdomain at this point.

Any help is appreciated!

Edit:


Since I want to use multiple other apps that may encounter similar problems, I would like to come up with a more general solution. In some cases I do not have control over the base URL.

Score:1
tf flag

There's a few of things going on here that we need to get on the same page for this to work:

  1. NGINX configuration: path based routing, reverse proxy
    • I've tried your config, and I believe your config is correct
  2. Apache2 configuration: correctly map URI to file paths and scripts
    • Use a directive AliasMatch "^/wiki(.*)" "/var/www/html/$1" to strip the /wiki and map to internal file paths correctly
  3. MediaWiki settings: add the /wiki path so that Nginx knows where to reverse proxy the links, and then Apache2 can strip out the /wiki and use the correct internal file paths.
    • Set $wgScriptPath = "/wiki"; in the LocalSettings.php file created after running the MediaWiki setup, and override that file in your docker-compose.yml
  4. Docker Compose: Persist those changes by mounting our modified local files into the mediawiki docker container

Apache2

We're going to add AliasMatch "^/wiki(.*)" "/var/www/html/$1" to the configuration at /etc/apache2/sites-enabled/000-default.conf. You'll also need to override the contents of this file in your docker-compose.yml

#/etc/apache2/sites-enabled/000-default.conf 
<VirtualHost *:80>
        # The ServerName directive sets the request scheme, hostname and port that
        # the server uses to identify itself. This is used when creating
        # redirection URLs. In the context of virtual hosts, the ServerName
        # specifies what hostname must appear in the request's Host: header to
        # match this virtual host. For the default virtual host (this file) this
        # value is not decisive as it is used as a last resort host regardless.
        # However, you must set it for any further virtual host explicitly.
        #ServerName www.example.com

        ServerAdmin webmaster@localhost
        DocumentRoot /var/www/html
        AliasMatch "^/wiki(.*)" "/var/www/html/$1"

        # Available loglevels: trace8, ..., trace1, debug, info, notice, warn,
        # error, crit, alert, emerg.
        # It is also possible to configure the loglevel for particular
        # modules, e.g.
        #LogLevel info ssl:warn

        ErrorLog ${APACHE_LOG_DIR}/error.log
        CustomLog ${APACHE_LOG_DIR}/access.log combined

        # For most configuration files from conf-available/, which are
        # enabled or disabled at a global level, it is possible to
        # include a line for only one particular virtual host. For example the
        # following line enables the CGI configuration for this host only
        # after it has been globally disabled with "a2disconf".
        #Include conf-available/serve-cgi-bin.conf
        AllowEncodedSlashes NoDecode
</VirtualHost>

MediaWiki settings:

This is just a snippet of the LocalSettings.php file that needs to be changed. It's a bit big to include the whole default here. It looks like you already have a local copy of LocalSettings.php that you're mounting into the docker container using docker-compose, so just modify the $wgScriptPath variable there and restart your container.

## The URL base path to the directory containing the wiki;
## defaults for all runtime URL paths are based off of this.
## For more information on customizing the URLs
## (like /w/index.php/Page_title to /wiki/Page_title) please see:
## https://www.mediawiki.org/wiki/Manual:Short_URL
$wgScriptPath = "/wiki";

Docker Compose

Now we'll modified the volumes section of the MediaWiki piece of the docker-compose.yml to override the apache2 config file for the MediaWiki site.

  mediawiki:
    image: mediawiki
    restart: unless-stopped
    hostname: mediawiki
    ports:
      - "8080:80"
    links:
      - database
    volumes:
      - ./images:/var/www/html/images
      - ./wiki/LocalSettings.php:/var/www/html/LocalSettings.php
      - ./apache2/000-default.conf:/etc/apache2/sites-enabled/000-default.conf
    networks:
      - wiki
yegorich avatar
cn flag
s/"/etc/apache2/sites-enabled/000-default.conf"/"/etc/apache2/sites-available/000-default.conf"
Score:0
us flag

Setting the root URL is the only stable solution to this issue.

I searched "mediawiki root URL", and second hit pointed to https://www.mediawiki.org/wiki/Topic:Ry90289pdqa86yrd, where the first response tells how to set the root URL in MediaWiki:

The URL to your wiki must be set in LocalSettings.php (just like e.g. $wgLogo, which is also set in that file) and you can change it by setting $wgServer to another value. This will then make MediaWiki output URLs starting with the domain you want.

user21101711 avatar
sy flag
Yes, I am aware of the configuration through LocalSettings.php and will certainly do so. However, I may encounter similar problems in other apps, e.g. a [Django admin page](https://docs.djangoproject.com/en/4.1/ref/contrib/admin/), and would therefore like to come up with a more generic solution.
us flag
It is always the CMS that generates the URLs. Therefore the only reliable solution is to set the root URL in the CMS.
I sit in a Tesla and translated this thread with Ai:

mangohost

Post an answer

Most people don’t grasp that asking a lot of questions unlocks learning and improves interpersonal bonding. In Alison’s studies, for example, though people could accurately recall how many questions had been asked in their conversations, they didn’t intuit the link between questions and liking. Across four studies, in which participants were engaged in conversations themselves or read transcripts of others’ conversations, people tended not to realize that question asking would influence—or had influenced—the level of amity between the conversationalists.