Unable to login: NoPermissionError

I’ve been using Ghost for probably 10+ years. I’m a software developer and linux admin. I don’t know everything, but this isn’t my first time setting up ghost, and my current blog has been running for a while. Newsletter emails (which I now understand to be separate from transactional emails) worked as I’ve posted a blog posted and received the corresponding email for it. Transactional emails seemed to not work in that I couldn’t register as a new user for my website.

Since updating recently from 5.x to 6.1 (and now 6.2 as of today), I notice that when I navigate to my ghost login page, there’s an error in the browser console logs immediately:

vendor-aed0068cf9b67…3a6343545b7b.js:492 
 GET https://website.com/blog/ghost/api/admin/users/me/?include=roles 403 (Forbidden)

This seems to correspond with the error I see in the ghost logs (ghost log -n 50):

[2025-10-05 14:22:00] ERROR “GET /blog/ghost/api/admin/users/me/?include=roles” 403 4ms

NAME: NoPermissionError
MESSAGE: Authorization failed

level: normal

“Unable to determine the authenticated user or integration. Check that cookies are being passed through if using session authentication.”
NoPermissionError: Authorization failed
at authorizeAdminApi (/var/www/website.com/blog/versions/6.2.0/core/server/services/auth/authorize.js:33:25)
at Layer.handle [as handle_request] (/var/www/website.com/blog/versions/6.2.0/node_modules/express/lib/router/layer.js:95:5)
at next (/var/www/website.com/blog/versions/6.2.0/node_modules/express/lib/router/route.js:149:13)
at authenticate (/var/www/website.com/blog/versions/6.2.0/core/server/services/auth/session/middleware.js:55:13)
at process.processTicksAndRejections (node:internal/process/task_queues:105:5)

That seems odd, since I haven’t even logged in yet (why would it request an object that requires authorization prior to authorization?). No matter, I continue with logging in anyways. After entering my login info and clicking “Login”, the page thinks for a good long while and eventually returns the ‘Server was unreachable’ error in the browser console logs:

ghost-2066304fd0b166…97dd73ef7b2.js:2893 O: Server was unreachable
    at p.handleResponse (ghost-2066304fd0b166…d73ef7b2.js:3957:48)
    at Object.<anonymous> (vendor-aed0068cf9b67…43545b7b.js:6746:70)
    at l (vendor-aed0068cf9b67…43545b7b.js:201:118)
    at Object.fireWith [as rejectWith] (vendor-aed0068cf9b67…43545b7b.js:202:694)
    at M (vendor-aed0068cf9b67…43545b7b.js:480:468)
    at XMLHttpRequest.<anonymous> (vendor-aed0068cf9b67…43545b7b.js:491:181)
vendor-aed0068cf9b67…3a6343545b7b.js:492 
 POST https://website.com/blog/ghost/api/admin/session net::ERR_CONNECTION_CLOSED

The following are my NGINX error logs (there are about 25 lines, they all have this same text (“upstream timed out (110: Unknown error) while reading response header from upstream”). Here is the latest entry:

2025/10/05 13:58:38 [error] 328742#328742: *83622 upstream timed out (110: Unknown error) while reading response header from upstream, client: 27.13.129.212, server: website.com, request: "POST /blog/ghost/api/admin/session HTTP/2.0", upstream: "http://127.0.0.1:2368/blog/ghost/api/admin/session", host: "website.com"

I’m just using regular Chrome browser, latest version. Tried Incognito mode (no extensions), same error. Tried with Firefox, same issue.

I found similar a similar issue reported here but it’s not clear whether it’s relevant to me. My NGINX is setup such that it redirects non-https traffic to with a return 301 to the https page instead. Here’s the section of my NGINX config relevant to ghost:

# ghost blog
location ^~ /blog {
  client_max_body_size 100m;
  proxy_buffer_size 16k;
  proxy_buffers 8 16k;
  proxy_busy_buffers_size 24k;
  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  proxy_set_header X-Forwarded-Proto $scheme;
  proxy_set_header X-Real-IP $remote_addr;
  proxy_set_header Host $host;
  proxy_pass http://127.0.0.1:2368;
}

Does anyone see anything therein that might cause issues, or just in general know how to resolve the error I’m experiencing?

Thanks!

Update a few hours later: I’ve spent another 2+ hours debugging this issue, and it’s clear that Ghost is happily serving everything else under /blog/ghost/…

Only the session-based admin request (/users/me/?include=roles) gets a 403.

The cookie looks correct and is being set. In fact, I explicitly forward the Cookie: header (proxy_set_header Cookie $http_cookie;). I commented out all my ‘common security configurations’ in NGINX in case they were the culprit, but the issue persists.

I thought it might be my Cloudflare proxy, so I disable it entirely but I get the same issue, except the one difference being that the login page doesn’t hang forever, instead there’s a banner at the top that says “There was a problem on the server” and the login button turns red and says “Retry”. When I check the logs, it’s the same error shown as above, however.

Update a few more hours later: Updated the description of the issue.

Update after it suddenly started working: I’m still not entirely sure what actually fixed the issue but I will provide everything I changed in case it helps a future person with this issue. If I had to guess, it was that transactional emails weren’t getting through properly. I had transactional emails setup correctly in my ghost config, using the following format:

  "mail": {
    "from": "'HappyTHoughts' <no-reply@happythoughts.com>",
    "transport": "SMTP",
    "options": {
      "service": "Mailgun",
      "host": "smtp.mailgun.org",
      "port": 465,
      "secure": true,
      "auth": {
        "user": "mail@mg.happythoughts.com",
        "pass": "<redacted>"
      }
    }
  },

However, for some reason they weren’t working in that I couldn’t subscribe as a new user. I thought it might be my VPS firewall, so I opened up ports 465 and 587 (TCP) on the firewall. At the same time, I thought there might be an issue with the secure mode so I went from using
port: 465 and secure: true, to port: 587 and secure: false in my ghost config.

After restarting ghost this worked. It might have been the firewall though and not specifically reverting to port: 587 and secure: false. Also, Cloudflare may have been an issue as well but I can’t confirm. Maybe at some point if I get some time I will try to return to port 465 and using Cloudflare to see if it still works, and then I’ll update this post.

The 403 error just means you need to login. It’s not really diagnostic.

I’m going to take a guess without enough data that you maybe don’t have transactional email set up, and you’re getting your hang right AFTER you submit your password. If that sounds right, then your problem is that recent versions of Ghost send a verification code by email for staff logins, and yours isn’t getting delivered correctly. (Could be a config problem on your end, or new blocking by your web host. Digital Ocean started blocking most outbound SMTP a few months ago, so it might not be anything you did.)

To get yourself logged in, add the following to your config.production.json (or do the equivalent with Docker env vars). Put it within the outermost set of { }, and separated from neighboring content by a comma. Then restart Ghost.

"security": {
  "staffDeviceVerification": false
}

That’s the quickest way to get yourself logged in. HOWEVER, if your site has members, THEY probably can’t log in either, so you’ll probably want to investigate why outbound email is failing. If you need help with debugging email, share a lot more about your configuration, please.

I have transactional emails setup in the only way I know how, which is in the ghost config. When I write new posts, emails get properly sent out using this configuration (using mailgun SMTP). Is there somewhere else I need to define it? I’m running ghost on my own VPS, no docker or anything, just good old fashioned manual install.

Yeah it’s not really a problem for me to be logged in – I am logged in with Firefox actually. I just want to fix the deeper issue because it may affect other users.

I think you’ve got some confusion here. Newsletters go out via bulk email, using the Mailgun API. (It’s not possible to send them via Mailgun SMTP. Only the Mailgun API, configured in the Ghost dashboard, works.) Verification codes and magic links go out however you have them configured in config.production.json. (That might be Mailgun SMTP, or it might not – there are lots options there.)

So, saying that your transactional email is working because your newsletter sends doesn’t make sense. :slight_smile:

If you want to check if transactional email is working, you could try signing in or subscribing from the public-facing website (not from /ghost). I’ll bet a nickel that it isn’t working.

Many VPS providers block outbound SMTP. The fact that yours presumably used to work doesn’t mean it’s still working, unfortunately. You may want to switch (in config.production.json) to using Mailgun’s API for outbound transactional email also.

Oops, I realized in editing my reply I conveyed the wrong information, but it doesn’t matter. Thanks for clarifying anyways. :slight_smile: I had forgotten about that section for configuring mailgun for bulk email delivery in the Ghost Settings.

You may want to switch (in config.production.json) to using Mailgun’s API for outbound transactional email also.

How do I do that? What are the lines in the config that I require?

Various sources offer conflicting or nonfunctional answers. I haven’t found one that works. For example:

  "mail": {
    "transport": "mailgun",
    "options": {
      "auth": {
        "api_key": "f43ff54-4d794a26",
        "domain": "mg.mysite.com"
      },
      "host": "api.mailgun.net"
    }
  }

I’ve also seen:

  "mail": {
    "transport": "Direct",
    "options": {
      "api": "fbceb7cb-4d761a26",
      "domain": "mg.mysite.com",
      "baseUrl": "https://api.mailgun.net/v3"
    }
  },

Couldn’t get any to work. Do you know the correct format?

Update: I returned to my previous SMTP configuration and now suddenly I can signup and login with my admin account. I have no clue why it suddenly works. But still, if you know how to properly setup the ghost config to work with the mailgun API instead of SMTP, I’d like to know so I can consider using that instead. Thank you. :slight_smile: