Gmail API authentication on headless server without browser access?

I’m working on a project where I need to connect to Gmail API from a headless AWS server. The problem is that the standard authentication flow tries to open a web browser which obviously doesn’t exist on my server.

When I run my authentication script, I get this error:

def setup_gmail_connection():
    credentials_file = 'client_credentials.json'
    scopes = ['https://www.googleapis.com/auth/gmail.readonly']
    
    oauth_flow = InstalledAppFlow.from_client_secrets_file(
        credentials_file, scopes)
    
    # This line fails on headless server
    auth_creds = oauth_flow.run_local_server(port=8080)
    return auth_creds

The error message says it cannot locate a runnable browser. This makes total sense since I’m running this on a cloud instance without any GUI.

Is there a way to handle Gmail API authentication without needing a browser? Maybe some kind of service account approach or a different authentication method that works better for server environments?

you can also grab your refresh token manually through google’s oauth playground. just hit up the oauth2 playground, pick the gmail api scopes, authorize it, and swap the code for tokens. then hardcode that refresh_token into your server app and skip the browser flow completely. it’s a bit hacky but works perfectly for production when you don’t want any manual steps.

Had this exact problem six months ago with a Gmail automation script on a remote server. Switched to service account authentication and it worked perfectly. Just create a service account in Google Cloud Console, download the JSON credentials, and use the google-auth library with service_account.Credentials.from_service_account_file(). One catch though - service accounts only work with Gmail if you’re on Google Workspace with domain-wide delegation enabled. For personal Gmail accounts, you’ll need to run the OAuth flow once on your local machine first. Save the refresh token, then use google.oauth2.credentials.Credentials with that token on your headless server. Do the initial auth locally, but after that API calls work fine since the refresh token handles renewals automatically.

Use run_console() instead of run_local_server() for headless setups. It’ll prompt you to visit an auth URL manually and paste the code back into your terminal. Just replace your failing line with auth_creds = oauth_flow.run_console(). The script will print a URL you can open on any device with a browser. Authorize it, and copy the code back to your server terminal. After this one-time auth, credentials are saved locally, and your app refreshes tokens automatically—no more browser needed. I’ve implemented this on several EC2 instances for Gmail API tasks. It requires manual work upfront, but afterward, it’s fully automated.