SSD Nodes Learn Hosting plans →
Guides Matt ConnorBy Matt Connor

Connect Gmail to self-hosted n8n

Connect Gmail to self-hosted n8n behind a reverse proxy, then stop the seven-day Google token expiry that kills Gmail workflows a week after setup.

How to connect Gmail to self-hosted n8n

To connect Gmail to self-hosted n8n, create a Google OAuth (open authorization) client of the type Web application. Then paste n8n's callback URL into it. If your server is behind a reverse proxy, that callback URL is only correct after you set N8N_EDITOR_BASE_URL to your public https:// address. After that you have to deal with the seven-day limit. An External Google app that stays in Testing loses its Gmail access one week after you sign in.

This guide starts where self-hosting n8n on a VPS with Docker and HTTPS ends. Your editor opens at an https:// address, and a reverse proxy sits in front of the container. Everything here was checked against n8n 2.41.6, the stable release of 2 October 2026. The Google details were checked against Google's documentation on 4 October 2026.

Why the callback URL is the self-hosted problem

OAuth works by redirect. n8n sends your browser to Google, and you approve the access. Google then sends your browser back to a callback URL on your n8n server, with a one-time code. Google only redirects to an address that is registered on the OAuth client. The match must be exact: the scheme, host, port and path must all agree.

n8n's Google credential docs show one example callback, http://localhost:5678/rest/oauth2-credential/callback. They say nothing about reverse proxies. A self-hosted n8n shows that same address when nothing else is configured, so a fresh install sends it to Google.

In n8n 2.41.6 the callback comes from packages/cli/src/services/url.service.ts. The instance base URL is chosen in this order:

  1. N8N_EDITOR_BASE_URL, if it is set.
  2. Otherwise the webhook base URL: N8N_WEBHOOK_URL first, then the older WEBHOOK_URL.
  3. Otherwise a URL built from N8N_PROTOCOL, N8N_HOST, N8N_PORT and N8N_PATH.

n8n then adds /rest/oauth2-credential/callback to the end. The same function feeds two places: the redirect URL in the credential modal, and the redirect_uri that n8n sends to Google. What you see in the modal is what Google receives.

Rule 3 is the trap. It only drops the port when the protocol is https and the port is 443. Behind a proxy, n8n still listens on 5678. So N8N_PROTOCOL=https plus N8N_HOST=n8n.example.com produces https://n8n.example.com:5678/rest/oauth2-credential/callback. Your proxy does not answer on that port, and Google does not have that URL on file.

Either variable fixes the modal. Many compose files set only WEBHOOK_URL. In that case the callback is already right, because the instance base URL falls back to it. Set N8N_EDITOR_BASE_URL anyway. It is the variable that wins, and it states clearly which address you mean. When both are set and they differ, n8n uses the editor URL for the callback. Note also that the 2.41.6 source marks WEBHOOK_URL as deprecated and replaces it with N8N_WEBHOOK_URL.

Set the public URL in your compose file

Add the public address to the environment block of the n8n service, and use your own domain. If n8n runs under a sub-path, such as https://example.com/n8n/, include the path.

services:
  n8n:
    environment:
      - N8N_HOST=n8n.example.com
      - N8N_PROTOCOL=https
      - N8N_EDITOR_BASE_URL=https://n8n.example.com/
      - WEBHOOK_URL=https://n8n.example.com/

Apply the change with up -d, not with restart. docker compose restart restarts the existing container with its old environment, so the change is silently ignored. docker compose up -d sees the changed configuration and recreates the container.

docker compose up -d
docker compose exec n8n printenv N8N_EDITOR_BASE_URL

The second command should print your https:// address. If your service is not named n8n, use the name from your compose file. A trailing slash is fine, because n8n removes it before it adds the callback path.

Now open n8n and start a new credential of the type Gmail OAuth2 API. Read the OAuth Redirect URL field. It should show https://n8n.example.com/rest/oauth2-credential/callback, with your domain in place of the example. If it still shows localhost or :5678, the container did not get the variable. Fix that before you touch Google, because every later step copies this value.

Step 1: create a Google Cloud project

Sign in to the Google Cloud console with the account that will own the app. Create a new project with a name you will recognise later, such as n8n-gmail. Before you continue, check that the project picker at the top of the console shows the new project. Every setting below belongs to the selected project, and it is easy to configure the wrong one.

Google renames its console pages often. So the steps below describe the field you fill, not the exact button label.

Step 2: enable the Gmail API

Open the API library for the project, search for Gmail API, and enable it. If you skip this step, the sign-in still succeeds. The first Gmail call then fails with an error that starts with Gmail API has not been used in project and tells you to enable the API.

This used to be one page called the OAuth consent screen. As of October 2026 Google splits it into sections, and branding and audience are separate pages. These are the fields you need:

  • App name and user support email. Only you will see them, so keep them simple.
  • Audience or user type: Internal or External. Internal only appears when the project belongs to a Google Workspace organisation, and it limits sign-in to members of that organisation. A personal @gmail.com account must use External.
  • Developer contact email. Google sends notices about the project to this address.
  • Authorized domains, in the branding section. Add the domain your n8n runs on, for example example.com.
  • Test users, in the audience section. Add the Gmail address you will connect. An External app in Testing refuses everyone who is not on this list, and their sign-in ends in an access_denied error.

Leave the publishing status at Testing for now. The seven-day section below explains when to change it.

Step 4: create a Web-application OAuth client

In the credentials or clients section, create an OAuth client ID with these fields:

  • Application type: Web application. A Desktop client has no redirect URI field, so it cannot use n8n's callback.
  • Name: any name, such as n8n.
  • Authorized redirect URIs: paste the OAuth Redirect URL from the n8n modal, exactly as shown.
  • Authorized JavaScript origins: leave this empty. It is a different list, and a callback pasted there does nothing.

Save the client. Google shows a client ID and a client secret. Paste both into the n8n credential, then select Sign in with Google. Google shows the consent screen for your app, and you approve the Gmail access. The n8n modal should now show the account as connected. Save the credential.

What redirect_uri_mismatch looks like

If the URL in the n8n modal and the URL on the Google client differ by even one character, Google stops the sign-in. The page has the heading Access blocked: This app's request is invalid and this text:

Error 400: redirect_uri_mismatch

You can't sign in to this app because it doesn't comply with Google's OAuth 2.0 policy.

If you're the app developer, register the redirect URI in the Google Cloud Console.

Open the request details on that page. They show the redirect_uri= value that n8n actually sent. Compare it with the list on your OAuth client. These are the common differences: http where you registered https, an extra :5678, localhost instead of your domain, and a missing sub-path. If the sent value is wrong, fix the n8n environment. If the registered value is wrong, fix the Google client and wait a few minutes. Google warns that changes to a client can take time to apply.

Google may accept the redirect and your browser may still land on a 502 or 404 page. In that case the callback request is not reaching n8n. That is a proxy problem, and how an nginx reverse proxy config passes requests to an app explains each directive that can cause it.

Step 5: build a Gmail trigger that fires

Create a workflow and add the Gmail Trigger node with your new credential. Set it up like this:

  • Event: Message Received. It is the only event.
  • Poll Times: every minute while you test.
  • Read Status filter: leave it at unread emails only.
  • Search filter: optional. It takes Gmail search syntax such as from:billing@example.com.

The Gmail Trigger polls. It asks Gmail for new messages on the schedule you set, so Google never needs to reach a webhook on your server. It only needs a valid token.

Send yourself a test email, then fetch a test event in the node. The output should show the message ID and its headers. Turn the workflow on so that the poll runs when the editor is closed. Depending on your version, the control is labelled Active or Publish. Send another email and wait one poll cycle. The executions list should show a successful run.

If the workflow stops working later, ask two questions. Is the token still valid? Is the container still running? The next section covers the token. A stopped container has its own causes, and why a self-hosted n8n keeps going offline covers them.

The seven-day cliff: why Gmail workflows die after a week

Most self-hosted Gmail setups hit this failure, and nothing warns you before it happens. n8n's Google docs state it plainly: for Google Cloud apps with the publishing status Testing and the user type External, "consent and tokens expire after seven days". Google's audience documentation says the same thing: "Authorizations by a test user will expire seven days from the time of consent."

This is how it happens. When you sign in, Google gives n8n a short-lived access token and a refresh token. n8n uses the refresh token to get a new access token each time the old one expires. For an External app in Testing, the refresh token itself expires seven days after consent. On day seven the refresh request fails and Google answers with invalid_grant. Every Gmail poll after that fails too.

Two details surprise people. The clock starts when you selected Sign in with Google, not when the workflow last ran. Signing in again only gives you another seven days. If a failed execution shows invalid_grant about a week after setup, the seven-day limit is the cause.

Choose one way out, based on the account you have.

Exit A: publish the External app to production

On the audience page, change the publishing status from Testing to In production. Refresh tokens issued after that change do not have the seven-day limit. Then select Sign in with Google in n8n one more time. The token you already hold was issued under the Testing rules, so you need a new one.

Publishing is not the same as verification. The scopes n8n requests by default include https://mail.google.com/ and https://www.googleapis.com/auth/gmail.modify. Google classes both as restricted. A public app with restricted scopes needs verification and a security assessment. Google also lists exceptions, which we checked on 4 October 2026. The exception that fits a self-hosted n8n is personal use. If the app is "for your personal use (fewer than 100 users)", you and your users can keep using it without verification.

What you get instead is the unverified-app screen. When you sign in, Google shows Google hasn't verified this app. Open the advanced option on that screen and continue to your app. This is normal for an app that only you use, and it only appears when someone grants consent. Google limits an app that shows this screen to 100 new users in total, and a personal n8n never comes near that number.

Production tokens can still stop working. Google stops accepting a refresh token when you remove the app's access in your Google Account settings. A refresh token with Gmail scopes also stops working when the account password changes. A token that is not used for six months expires. Google also keeps at most 100 live refresh tokens per account for one client, so if you sign in many times, the oldest token is dropped. Each case ends in the same invalid_grant error, and the fix is to sign in again.

Exit B: an Internal app on a Google Workspace domain

If the Gmail address belongs to a Google Workspace domain, create the project inside that organisation and set the audience to Internal. Google applies the seven-day rule only to External apps in Testing, so it does not affect an Internal app. Google's verification exceptions also cover internal use. An app "only used by people in your Google Workspace or Cloud Identity organization" does not get the unverified-app screen or the user limit.

This only works when the project is in the organisation and the person who signs in is a member of it. A personal @gmail.com address cannot use an Internal app.

Exit C: IMAP and SMTP with an app password

You can skip OAuth entirely. Gmail supports IMAP (Internet Message Access Protocol) for reading mail and SMTP (Simple Mail Transfer Protocol) for sending it. Since January 2025, IMAP is always on in Gmail, so there is no setting to change.

You log in with an app password: a 16-character code that Google generates for one app. It requires 2-Step Verification on the account. Google does not offer app passwords on accounts with Advanced Protection or on work and school accounts. They are also not available when 2-Step Verification uses only security keys.

In n8n, read mail with the Email Trigger (IMAP) node and an IMAP credential. Use host imap.gmail.com, port 993, with SSL/TLS on. Send mail with the Send Email node and an SMTP credential. Use host smtp.gmail.com, port 465, with SSL/TLS on. The user is the full Gmail address, and the password is the app password with no spaces.

This option has real costs. An app password gives access to the whole mailbox, and Google's own help says app passwords are not recommended. When you change your Google password, Google revokes every app password, so the workflow stops until you create a new one. You also lose the Gmail-specific filters of the Gmail Trigger, and IMAP shows Gmail labels as plain folders.

Outlook: the same callback in a Microsoft Entra app

Microsoft mail uses the same pattern. n8n builds every OAuth2 callback from the same instance base URL. Once the Gmail credential shows the right redirect URL, a Microsoft credential shows the same URL.

n8n's Microsoft credential docs describe the registration. Open the app registrations page, which is part of Microsoft Entra, and create a registration with a name. For supported account types, n8n's docs choose "Accounts in any organizational directory (Any Azure AD directory - Multi-tenant) and personal Microsoft accounts". Set the redirect URI platform to Web and paste n8n's OAuth Redirect URL. Copy the Application (client) ID into n8n as the client ID. Create a client secret in the certificates and secrets section, and copy its value into n8n. If an organisation manages the Microsoft account, an administrator may have to approve the app first. n8n's documentation stops at this point, and this guide does too.

Which exit should you pick?

For a personal Gmail account, publish the External app to production and continue past the unverified-app screen once. For a Workspace mailbox, use an Internal app. Choose IMAP with an app password when you cannot create a Google Cloud project, or when the workflow only reads and sends mail.

You do not need a paid plan for any of this. The Gmail nodes, the IMAP node and OAuth credentials are all part of the free self-hosted Community Edition of n8n. If you are still deciding whether n8n fits your work, what n8n is and how its workflows run explains the basics.

FAQ

Why does my n8n Gmail credential show a localhost redirect URL?

n8n builds the OAuth Redirect URL from N8N_EDITOR_BASE_URL. If that is not set, it uses the webhook URL. If neither is set, it builds the URL from N8N_PROTOCOL, N8N_HOST, N8N_PORT and N8N_PATH. With none of them set, the result is http://localhost:5678/rest/oauth2-credential/callback. Set N8N_EDITOR_BASE_URL to your public https:// address in the container environment. Then recreate the container with docker compose up -d and open the credential again.

Why did my n8n Gmail trigger stop working after seven days?

Your Google app is External and still in Testing. Google expires a test user's authorization seven days after consent. n8n's refresh token then stops working, and each poll fails with invalid_grant. Signing in again only starts another seven days. For a personal Gmail account, the lasting fix is to publish the app to production and sign in once more. On a Google Workspace domain, an Internal app avoids the limit completely.

Do I need Google verification to use Gmail with self-hosted n8n?

Not for personal use. n8n requests restricted Gmail scopes, and a public app with restricted scopes needs verification. Google exempts apps for personal use with fewer than 100 users. When you sign in, you will see the "Google hasn't verified this app" screen, and you can continue past it to your own app.

Does the Gmail trigger need a public webhook URL?

No. The Gmail Trigger polls Gmail on the schedule in Poll Times, so Google never calls your server. The OAuth callback is a browser redirect. Your own browser must be able to reach the callback URL when you sign in, and that URL must match the one registered on the Google client.

#n8n#gmail#google-oauth#self-hosting#automation