[GH-ISSUE #991] Authentication issue - 40301 "Error publishing notification" #695

Closed
opened 2026-05-07 00:26:39 +02:00 by BreizhHardware · 4 comments

Originally created by @geekykant on GitHub (Dec 26, 2023).
Original GitHub issue: https://github.com/binwiederhier/ntfy/issues/991

🐞 Describe the bug
I have setup authentication in ntfy to strictly allow only registered users to use the platform.
However publishing a notification throws 403 HTTP error.

$ curl -u myusername:mypass -d "testing message" https://<base-url>/mytopic
{"code":40301,"http":403,"error":"forbidden","link":"https://ntfy.sh/docs/publish/#authentication"}

Here is my config:
docker-compose.yml

version: "3"

services:
  ntfy:
    image: binwiederhier/ntfy
    container_name: ntfy-server
    command:
      - serve
    environment:
      NTFY_BASE_URL: <base-url>
      NTFY_UPSTREAM_BASE_URL: https://ntfy.sh
      NTFY_CACHE_FILE: /var/lib/ntfy/cache.db
      NTFY_AUTH_FILE: /var/lib/ntfy/auth.db
      NTFY_AUTH_DEFAULT_ACCESS: deny-all
      NTFY_BEHIND_PROXY: 1
      NTFY_ATTACHMENT_CACHE_DIR: /var/lib/ntfy/attachments
      NTFY_WEB_PUSH_FILE: /var/lib/ntfy/webpush.db
      NTFY_ENABLE_LOGIN: 1
      NTFY_ENABLE_SIGNUP: 1
    volumes:
      - /var/cache/ntfy:/var/cache/ntfy
      - /etc/ntfy:/etc/ntfy
      - ./:/var/lib/ntfy
    ports:
      - 2000:80
    healthcheck: # optional: remember to adapt the host:port to your environment
        test: ["CMD-SHELL", "wget -q --tries=1 http://localhost:80/v1/health -O - | grep -Eo '\"healthy\"\\s*:\\s*true' || exit 1"]
        interval: 60s
        timeout: 10s
        retries: 3
        start_period: 40s
    restart: unless-stopped

nginx config

server {
    if ($host = <base-url>) {
        return 301 https://$host$request_uri;
    } # managed by Certbot


  listen 80;
  server_name <base-url>;

  location / {
    # Redirect HTTP to HTTPS, but only for GET topic addresses, since we want
    # it to work with curl without the annoying https:// prefix
    set $redirect_https "";
    if ($request_method = GET) {
      set $redirect_https "yes";
    }
    if ($request_uri ~* "^/([-_a-z0-9]{0,64}$|docs/|static/)") {
      set $redirect_https "${redirect_https}yes";
    }
    if ($redirect_https = "yesyes") {
      return 302 https://$http_host$request_uri$is_args$query_string;
    }

    proxy_pass http://127.0.0.1:2000;
    proxy_http_version 1.1;

    proxy_buffering off;
    proxy_request_buffering off;
    proxy_redirect off;

    proxy_set_header Host $http_host;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

    proxy_connect_timeout 3m;
    proxy_send_timeout 3m;
    proxy_read_timeout 3m;

    client_max_body_size 0; # Stream request body to backend
  }
}

server {
  listen 443 ssl http2;
  server_name <base-url>;

  # See https://ssl-config.mozilla.org/#server=nginx&version=1.18.0&config=intermediate&openssl=1.1.1k&hsts=false&ocsp=false&guideline=5.6see https://ssl-config.mozilla.org/#server=nginx&version=1.18.0&config=intermediate&openssl=1.1.1k&hsts=false&ocsp=false&guideline=5.6
  ssl_session_timeout 1d;
  ssl_session_cache shared:MozSSL:10m; # about 40000 sessions
  ssl_session_tickets off;
  ssl_protocols TLSv1.2 TLSv1.3;
  ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
  ssl_prefer_server_ciphers off;

  location / {
    proxy_pass http://127.0.0.1:2000;
    proxy_http_version 1.1;

    proxy_buffering off;
    proxy_request_buffering off;
    proxy_redirect off;

    proxy_set_header Host $http_host;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

    proxy_connect_timeout 3m;
    proxy_send_timeout 3m;
    proxy_read_timeout 3m;

    client_max_body_size 0; # Stream request body to backend
  }

    ssl_certificate /etc/letsencrypt/live/<base-url>/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/<base-url>/privkey.pem; # managed by Certbot
}

💻 Components impacted

  • Self-hosted ntfy server (Docker + NGINX reverse proxy)
  • iOS

💡 Screenshots and/or logs

  • It shows "Error publishing notification" when trying to publish a simple message from the web UI after logging in.
  • The topic also shows progressing animation for a long time.

The console logs I see are:

WebSocket connection to 'wss://<base-url>/st_****/ws?auth=*********81cHFqcGhmNXpnZ3pxOTJidXg2' failed: 
Connection.js:80 [Connection, <base-url>/st_****, -224670172] Error occurred: [object Event] Event
Connection.js:74 [Connection, <base-url>/st_****, -224670172] Connection died, retrying in 120 seconds

Screenshot 2023-12-26 at 2 00 39 PM
Screenshot 2023-12-26 at 1 19 40 PM

🔮 Additional context
The only additional change I added is authentication. WIthout that, it works perfectly fine.
I don't understand exactly why this error message comes up other than 403 error and webSocket connection failed.

Originally created by @geekykant on GitHub (Dec 26, 2023). Original GitHub issue: https://github.com/binwiederhier/ntfy/issues/991 :lady_beetle: **Describe the bug** I have setup authentication in ntfy to strictly allow only registered users to use the platform. However publishing a notification throws 403 HTTP error. ``` $ curl -u myusername:mypass -d "testing message" https://<base-url>/mytopic {"code":40301,"http":403,"error":"forbidden","link":"https://ntfy.sh/docs/publish/#authentication"} ``` Here is my config: **docker-compose.yml** ```yaml version: "3" services: ntfy: image: binwiederhier/ntfy container_name: ntfy-server command: - serve environment: NTFY_BASE_URL: <base-url> NTFY_UPSTREAM_BASE_URL: https://ntfy.sh NTFY_CACHE_FILE: /var/lib/ntfy/cache.db NTFY_AUTH_FILE: /var/lib/ntfy/auth.db NTFY_AUTH_DEFAULT_ACCESS: deny-all NTFY_BEHIND_PROXY: 1 NTFY_ATTACHMENT_CACHE_DIR: /var/lib/ntfy/attachments NTFY_WEB_PUSH_FILE: /var/lib/ntfy/webpush.db NTFY_ENABLE_LOGIN: 1 NTFY_ENABLE_SIGNUP: 1 volumes: - /var/cache/ntfy:/var/cache/ntfy - /etc/ntfy:/etc/ntfy - ./:/var/lib/ntfy ports: - 2000:80 healthcheck: # optional: remember to adapt the host:port to your environment test: ["CMD-SHELL", "wget -q --tries=1 http://localhost:80/v1/health -O - | grep -Eo '\"healthy\"\\s*:\\s*true' || exit 1"] interval: 60s timeout: 10s retries: 3 start_period: 40s restart: unless-stopped ``` **nginx config** ```nginx server { if ($host = <base-url>) { return 301 https://$host$request_uri; } # managed by Certbot listen 80; server_name <base-url>; location / { # Redirect HTTP to HTTPS, but only for GET topic addresses, since we want # it to work with curl without the annoying https:// prefix set $redirect_https ""; if ($request_method = GET) { set $redirect_https "yes"; } if ($request_uri ~* "^/([-_a-z0-9]{0,64}$|docs/|static/)") { set $redirect_https "${redirect_https}yes"; } if ($redirect_https = "yesyes") { return 302 https://$http_host$request_uri$is_args$query_string; } proxy_pass http://127.0.0.1:2000; proxy_http_version 1.1; proxy_buffering off; proxy_request_buffering off; proxy_redirect off; proxy_set_header Host $http_host; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_connect_timeout 3m; proxy_send_timeout 3m; proxy_read_timeout 3m; client_max_body_size 0; # Stream request body to backend } } server { listen 443 ssl http2; server_name <base-url>; # See https://ssl-config.mozilla.org/#server=nginx&version=1.18.0&config=intermediate&openssl=1.1.1k&hsts=false&ocsp=false&guideline=5.6see https://ssl-config.mozilla.org/#server=nginx&version=1.18.0&config=intermediate&openssl=1.1.1k&hsts=false&ocsp=false&guideline=5.6 ssl_session_timeout 1d; ssl_session_cache shared:MozSSL:10m; # about 40000 sessions ssl_session_tickets off; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; ssl_prefer_server_ciphers off; location / { proxy_pass http://127.0.0.1:2000; proxy_http_version 1.1; proxy_buffering off; proxy_request_buffering off; proxy_redirect off; proxy_set_header Host $http_host; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_connect_timeout 3m; proxy_send_timeout 3m; proxy_read_timeout 3m; client_max_body_size 0; # Stream request body to backend } ssl_certificate /etc/letsencrypt/live/<base-url>/fullchain.pem; # managed by Certbot ssl_certificate_key /etc/letsencrypt/live/<base-url>/privkey.pem; # managed by Certbot } ``` :computer: **Components impacted** - Self-hosted ntfy server (Docker + NGINX reverse proxy) - iOS :bulb: **Screenshots and/or logs** - It shows "Error publishing notification" when trying to publish a simple message from the web UI after logging in. - The topic also shows progressing animation for a long time. The console logs I see are: ``` WebSocket connection to 'wss://<base-url>/st_****/ws?auth=*********81cHFqcGhmNXpnZ3pxOTJidXg2' failed: Connection.js:80 [Connection, <base-url>/st_****, -224670172] Error occurred: [object Event] Event Connection.js:74 [Connection, <base-url>/st_****, -224670172] Connection died, retrying in 120 seconds ``` ![Screenshot 2023-12-26 at 2 00 39 PM](https://github.com/binwiederhier/ntfy/assets/27401142/660f9370-b766-4ded-92d0-6498cdb184e9) ![Screenshot 2023-12-26 at 1 19 40 PM](https://github.com/binwiederhier/ntfy/assets/27401142/48c1e1fd-b1d5-4b4a-9cda-8c2abde42813) :crystal_ball: **Additional context** The only additional change I added is authentication. WIthout that, it works perfectly fine. I don't understand exactly why this error message comes up other than 403 error and webSocket connection failed.
BreizhHardware 2026-05-07 00:26:39 +02:00
  • closed this issue
  • added the
    🪲 bug
    label
Author
Owner

@geekykant commented on GitHub (Dec 26, 2023):

Sorry, false alarm. But still valid for an enhancement as a proper message wasn't displayed in the UI, nor the REST response.

Solution: I missed to setup the Access control part, which sets permission for users to each topic. So go inside the docker container, and run the nfty access commands.

Troubleshooted it from the docker trace logs:

{"level":"TRACE","message":"HTTP request started","http_method":"GET","http_path":"/<topic>/ws?auth=**OTJidXg2","http_request":"GET /<topic>/ws?auth=**OTJidXg2 HTTP/1.1\nPragma: no-cache\nOrigin: https://<base-url>\nAccept-Encoding: gzip, deflate, br\nConnection: upgrade\nX-Forwarded-For: 111.92.127.132\nAccept-Language: en-IN,en-GB;q=0.9,en;q=0.8,en-US;q=0.7,hi;q=0.6\nSec-Websocket-Extensions: permessage-deflate; client_max_window_bits\nSec-Websocket-Key: fpmT/qWG3A1zjYXDGfAGQg==\nUpgrade: websocket\nCache-Control: no-cache\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0\nSec-Websocket-Version: 13","tag":"http","user_id":"u_rYmbLhQnu2","user_name":"geekykant","visitor_auth_limiter_limit":0.016666666666666666,"visitor_auth_limiter_tokens":30,"visitor_id":"ip:111.92.127.132","visitor_ip":"111.92.127.132","visitor_messages":0,"visitor_messages_limit":17280,"visitor_messages_remaining":17280,"visitor_request_limiter_limit":0.2,"visitor_request_limiter_tokens":59.05817581699999,"visitor_seen":"2023-12-26T08:21:51.766Z"}
{"level":"DEBUG","message":"Access to topic <topic> not authorized","error":"unauthorized","http_method":"GET","http_path":"/<topic>/ws?auth=**OTJidXg2","tag":"http","topic":"<topic>","topic_last_access":"2023-12-26T08:21:16.475Z","topic_subscribers":0,"user_id":"u_rYmbLhQnu2","user_name":"geekykant","visitor_auth_limiter_limit":0.016666666666666666,"visitor_auth_limiter_tokens":30,"visitor_id":"ip:111.92.127.132","visitor_ip":"111.92.127.132","visitor_messages":0,"visitor_messages_limit":17280,"visitor_messages_remaining":17280,"visitor_request_limiter_limit":0.2,"visitor_request_limiter_tokens":58.05823781519999,"visitor_seen":"2023-12-26T08:21:51.766Z"}
{"level":"DEBUG","message":"WebSocket error (this error is okay, it happens a lot): forbidden","error":"forbidden","error_code":40301,"http_method":"GET","http_path":"/<topic>/ws?auth=**OTJidXg2","http_status":403,"tag":"websocket","topic":"<topic>","topic_last_access":"2023-12-26T08:21:16.475Z","topic_subscribers":0,"user_id":"u_rYmbLhQnu2","user_name":"geekykant","visitor_auth_limiter_limit":0.016666666666666666,"visitor_auth_limiter_tokens":30,"visitor_id":"ip:111.92.127.132","visitor_ip":"111.92.127.132","visitor_messages":0,"visitor_messages_limit":17280,"visitor_messages_remaining":17280,"visitor_request_limiter_limit":0.2,"visitor_request_limiter_tokens":58.05825770499999,"visitor_seen":"2023-12-26T08:21:51.766Z"}
<!-- gh-comment-id:1869405044 --> @geekykant commented on GitHub (Dec 26, 2023): Sorry, false alarm. But still valid for an enhancement as a proper message wasn't displayed in the UI, nor the REST response. **Solution**: I missed to setup the [Access control](https://docs.ntfy.sh/config/#access-control) part, which sets permission for users to each topic. So go inside the docker container, and run the nfty access commands. Troubleshooted it from the docker trace logs: ```json {"level":"TRACE","message":"HTTP request started","http_method":"GET","http_path":"/<topic>/ws?auth=**OTJidXg2","http_request":"GET /<topic>/ws?auth=**OTJidXg2 HTTP/1.1\nPragma: no-cache\nOrigin: https://<base-url>\nAccept-Encoding: gzip, deflate, br\nConnection: upgrade\nX-Forwarded-For: 111.92.127.132\nAccept-Language: en-IN,en-GB;q=0.9,en;q=0.8,en-US;q=0.7,hi;q=0.6\nSec-Websocket-Extensions: permessage-deflate; client_max_window_bits\nSec-Websocket-Key: fpmT/qWG3A1zjYXDGfAGQg==\nUpgrade: websocket\nCache-Control: no-cache\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0\nSec-Websocket-Version: 13","tag":"http","user_id":"u_rYmbLhQnu2","user_name":"geekykant","visitor_auth_limiter_limit":0.016666666666666666,"visitor_auth_limiter_tokens":30,"visitor_id":"ip:111.92.127.132","visitor_ip":"111.92.127.132","visitor_messages":0,"visitor_messages_limit":17280,"visitor_messages_remaining":17280,"visitor_request_limiter_limit":0.2,"visitor_request_limiter_tokens":59.05817581699999,"visitor_seen":"2023-12-26T08:21:51.766Z"} {"level":"DEBUG","message":"Access to topic <topic> not authorized","error":"unauthorized","http_method":"GET","http_path":"/<topic>/ws?auth=**OTJidXg2","tag":"http","topic":"<topic>","topic_last_access":"2023-12-26T08:21:16.475Z","topic_subscribers":0,"user_id":"u_rYmbLhQnu2","user_name":"geekykant","visitor_auth_limiter_limit":0.016666666666666666,"visitor_auth_limiter_tokens":30,"visitor_id":"ip:111.92.127.132","visitor_ip":"111.92.127.132","visitor_messages":0,"visitor_messages_limit":17280,"visitor_messages_remaining":17280,"visitor_request_limiter_limit":0.2,"visitor_request_limiter_tokens":58.05823781519999,"visitor_seen":"2023-12-26T08:21:51.766Z"} {"level":"DEBUG","message":"WebSocket error (this error is okay, it happens a lot): forbidden","error":"forbidden","error_code":40301,"http_method":"GET","http_path":"/<topic>/ws?auth=**OTJidXg2","http_status":403,"tag":"websocket","topic":"<topic>","topic_last_access":"2023-12-26T08:21:16.475Z","topic_subscribers":0,"user_id":"u_rYmbLhQnu2","user_name":"geekykant","visitor_auth_limiter_limit":0.016666666666666666,"visitor_auth_limiter_tokens":30,"visitor_id":"ip:111.92.127.132","visitor_ip":"111.92.127.132","visitor_messages":0,"visitor_messages_limit":17280,"visitor_messages_remaining":17280,"visitor_request_limiter_limit":0.2,"visitor_request_limiter_tokens":58.05825770499999,"visitor_seen":"2023-12-26T08:21:51.766Z"} ```
Author
Owner

@geekykant commented on GitHub (Dec 26, 2023):

@binwiederhier Can we move this to enhancement - to possibly have proper failure messages displayed for access-control and permission related issues?

<!-- gh-comment-id:1869410223 --> @geekykant commented on GitHub (Dec 26, 2023): @binwiederhier Can we move this to enhancement - to possibly have proper failure messages displayed for access-control and permission related issues?
Author
Owner

@snex commented on GitHub (Dec 8, 2024):

I am having this same issue even though I have set up my access control and the command line on the server shows my user as an admin.

edit: Weird.. it was happening because I was posting to the http url which should automatically forward to the https url. Apparently it is not forwarding something that the app thinks is important.

<!-- gh-comment-id:2525364370 --> @snex commented on GitHub (Dec 8, 2024): I am having this same issue even though I have set up my access control and the command line on the server shows my user as an admin. edit: Weird.. it was happening because I was posting to the http url which should automatically forward to the https url. Apparently it is not forwarding something that the app thinks is important.
Author
Owner

@anecdotal-testimonio commented on GitHub (Nov 18, 2025):

Hello,

I was having the same Issue. In my case I wanted to use unified push via ntfy.

I then realized that I needed to allow anonymous write only access to ntfy topics as described here and here

Hope this helps

<!-- gh-comment-id:3548171720 --> @anecdotal-testimonio commented on GitHub (Nov 18, 2025): Hello, I was having the same Issue. In my case I wanted to use unified push via ntfy. I then realized that I needed to allow anonymous write only access to ntfy topics as described [here](https://docs.ntfy.sh/config/#example-unifiedpush) and [here](https://unifiedpush.org/users/distributors/ntfy/#step-4-allow-unifiedpush-notifications) Hope this helps
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/ntfy#695
No description provided.