NGINX reverse proxy configuration after version 2.1

Hello there,

I have been using Duplicati a long time to backup my Next Cloud.
Duplicati is installed on the same host as Next Cloud so therefore I use the reverse proxy functionality of NGINX with a subdirectory (also the web server for Next Cloud) to reach the web interface of Duplicati and protect it with the same certificate as Next Cloud.
Now that I have updated to 2.1.0.3 the reverse proxy does not work anymore - did use it before without password authentication - and I tried many different configurations for NGINX.
I also configured a password with the necessary steps so I does work like this:
http://:8400

My old NGINX config working with version 2.0.x of Duplicati looks like this:

location ^~ /backup/ {
  proxy_pass http://<IP address of host>:8200/;
  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;
}

I also tried to issue a forever token (ServerUtil | Duplicati) and modified my NGINX config like this:

location ^~ /backup/ {
        proxy_set_header Authorization "Bearer <token>";
        proxy_pass http://<IP address>:8200/;
        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;
}

Is there anyone out there who had the same problem and does know how to solve this?

Thanks in advance,

Markus

Hi Markus,
I found here Can't access Duplicati through nginx - Docker - openmediavault a solution. You need to add “websocket” options to the proxy configuration.
After adding

    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";

to the nginx proxy configuration it worked for me.
Best regards,
Michael

Hi Micheal,

thank you so much for trying to help me.

First things first:
Duplicati is installed on Debian 12 without a GUI (headless server).

I added your two lines (tried something like this a few days ago after I “consulted” ChatGPT), but I am still stuck in a loop showing a message window with Duplicati in the background:

Connection lost: Connection to server was rejected due to invalid authentication.
Log in again, or re-open the page from the TrayIcon (if applicable)
Buttons: Help, Log in, and Reload

When I press reload or login, same message and screen appears in Chrome, in Safari I get to the login (pressing the login button), but after logging in getting back in the loop again.

I paste my complete nginx conf here for more information:

server {
  listen 443      ssl default_server;
  http2 on;
  server_name next.cloud;
  ssl_certificate /etc/ssl/certs/next.cloud.crt;
  ssl_certificate_key /etc/ssl/private/next.cloud.key;
  ssl_trusted_certificate /etc/ssl/certs/outerheavenlocal-rootCA-cert.pem;
  #ssl_certificate /etc/letsencrypt/rsa-certs/fullchain.pem;
  #ssl_certificate_key /etc/letsencrypt/rsa-certs/privkey.pem;
  #ssl_certificate /etc/letsencrypt/ecc-certs/fullchain.pem;
  #ssl_certificate_key /etc/letsencrypt/ecc-certs/privkey.pem;
  #ssl_trusted_certificate /etc/letsencrypt/ecc-certs/chain.pem;
  ssl_dhparam /etc/ssl/certs/dhparam.pem;
  ssl_session_timeout 1d;
  ssl_session_cache shared:SSL:50m;
  ssl_session_tickets off;
  ssl_protocols TLSv1.3 TLSv1.2;
  ssl_ciphers 'TLS-CHACHA20-POLY1305-SHA256:TLS-AES-256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384';
  ssl_ecdh_curve X448:secp521r1:secp384r1;
  ssl_prefer_server_ciphers on;
  ssl_stapling on;
  ssl_stapling_verify on;
  client_max_body_size 10G;
  client_body_timeout 3600s;
  client_body_buffer_size 512k;
  fastcgi_buffers 64 4K;
  gzip on;
  gzip_vary on;
  gzip_comp_level 4;
  gzip_min_length 256;
  gzip_proxied expired no-cache no-store private no_last_modified no_etag auth;
  gzip_types application/atom+xml application/javascript application/json application/ld+json application/manifest+json application/rss+xml application/vnd.geo+json application/vnd.ms-fontobject application/wasm application/x-font-ttf application/x-web-app-manifest+json application/xhtml+xml application/xml font/opentype image/bmp image/svg+xml image/x-icon text/cache-manifest text/css text/plain text/vcard text/vnd.rim.location.xloc text/vtt text/x-component text/x-cross-domain-policy;
  add_header Strict-Transport-Security            "max-age=15768000; includeSubDomains; preload;" always;
  add_header Permissions-Policy                   "interest-cohort=()";
  add_header Referrer-Policy                      "no-referrer"   always;
  add_header X-Content-Type-Options               "nosniff"       always;
  add_header X-Download-Options                   "noopen"        always;
  add_header X-Frame-Options                      "SAMEORIGIN"    always;
  add_header X-Permitted-Cross-Domain-Policies    "none"          always;
  add_header X-Robots-Tag                         "noindex, nofollow" always;
  add_header X-XSS-Protection                     "1; mode=block" always;
  fastcgi_hide_header X-Powered-By;
  include mime.types;
  types {
    text/javascript mjs;
  }
  root /var/www/nextcloud;
  index index.php index.html /index.php$request_uri;
  location = / {
    if ( $http_user_agent ~ ^DavClnt ) {
      return 302 /remote.php/webdav/$is_args$args;
    }
  }
  location = /robots.txt {
    allow all;
    log_not_found off;
    access_log off;
  }
  location ^~ /.well-known {
    location = /.well-known/carddav { return 301 /remote.php/dav/; }
    location = /.well-known/caldav  { return 301 /remote.php/dav/; }
    location /.well-known/acme-challenge { try_files $uri $uri/ =404; }
    location /.well-known/pki-validation { try_files $uri $uri/ =404; }
    return 301 /index.php$request_uri;
  }
  location ~ ^/(?:build|tests|config|lib|3rdparty|templates|data)(?:$|/)  { return 404; }
  location ~ ^/(?:\.|autotest|occ|issue|indie|db_|console)                { return 404; }
  location ~ \.php(?:$|/) {
    rewrite ^/(?!index|remote|public|cron|core\/ajax\/update|status|ocs\/v[12]|updater\/.+|ocs-provider\/.+|.+\/richdocumentscode\/proxy) /index.php$request_uri;
    fastcgi_split_path_info ^(.+?\.php)(/.*)$;
    set $path_info $fastcgi_path_info;
    try_files $fastcgi_script_name =404;
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_param PATH_INFO $path_info;
    fastcgi_param HTTPS on;
    fastcgi_param modHeadersAvailable true;
    fastcgi_param front_controller_active true;
    fastcgi_pass php-handler;
    fastcgi_intercept_errors on;
    fastcgi_request_buffering off;
    fastcgi_read_timeout 3600;
    fastcgi_send_timeout 3600;
    fastcgi_connect_timeout 3600;
    fastcgi_max_temp_file_size 0;
  }
  location ~ \.(?:css|js|mjs|svg|gif|png|jpg|ico|wasm|tflite|map)$ {
    try_files $uri /index.php$request_uri;
    add_header Cache-Control "public, max-age=15778463, $asset_immutable";
    expires 6M;
    access_log off;
    location ~ \.wasm$ {
      default_type application/wasm;
    }
  }
  location ~ \.woff2?$ {
    try_files $uri /index.php$request_uri;
    expires 7d;
    access_log off;
  }
  location /remote {
    return 301 /remote.php$request_uri;
  }
  
  location ^~ /backup/ {
    proxy_set_header Authorization "Bearer %token%";
    proxy_pass http://127.0.0.1:8200/;
    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;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection “upgrade”;
  }
  
  location / {
    try_files $uri $uri/ /index.php$request_uri;
  }
}

Thanks in advance for your help.

Cheers,
Markus

It sounds like the websocket is failing to connect.

Can you try the developer tools in the browser and see in the network tab if there are any errors?

Hi,
sorry for taking so many days to answer but my youngest got an op last Friday.
I check the output in the developer tools and got this:

Refused to apply style from ‘https://next.cloud/backup/ngax/styles/dark.css’ because its MIME type (‘text/html’) is not a supported stylesheet MIME type, and strict MIME checking is enabled.

The same for default.css and then many like this:

GET https://next.cloud/backup/ngax/scripts/controllers/EditBackupController.js?v=2.1.0.4 net::ERR_ABORTED 404 (Not Found)Understand this errorAI
index.html:81

Funny thing though: I didn’t change the config of nginx after updating from 2.0.x. to 2.1.x

Thanks for your help in advance.

Cheers

I am thinking something else is being served here? The MIME type is always set by the Duplicati server. Could this be a text document? Error page or similar?

I can see from the config that there are is some extra configuration in the file that mentions .css files with a try_files command.

That is the correct path. I asked ChatGPT for a fix, and it suggest that you need a URL rewrite to map /backup/ngax/... to /ngax/...:

  location ^~ /backup/ {
    rewrite ^/backup/(.*)$ /$1 break;
    proxy_set_header Authorization "Bearer %token%";
    proxy_pass http://127.0.0.1:8200/;
    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;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection “upgrade”;
  }

I have not tested it, but the answer makes sense to me.

Thank you so much for trying to help me.
I gave it a shot, but when I try https://next.cloud/backup I am redirected to https://next.cloud/backup/ngax/index.html and I get the “Connection lost” - with “Help”, “Log in”, and “Reload”. “Reload” loads the same screen and “Log in” redirects to https://next.cloud/login (no backup directory…). That results in a page not found from Next Cloud. When I enter https://next.cloud/backup/login then I reach the login screen, but after entering my credentials get to the “Connection lost” again…

And developer mode shows: Failed to load resource: the server responded with a status of 401 () /backup/api/v1/auth/refresh:1

I also tried “Duplicati - 2.1.0.4_stable_2025-01-31” - same prob.