If you’ve deployed Strapi v5 on Heroku and tried integrating Google OAuth, you may have run into this dreaded error:

Error: Cannot send secure cookie over unencrypted connection
It typically appears right after hitting your Google login endpoint:

/api/connect/google
Even though your app is running over HTTPS, Strapi (via koa-session) thinks the request is not secure, and refuses to set a Secure cookie. Here’s why it happens  and how to fix it.

Why This Happens

When you deploy on Heroku:

  • HTTPS traffic is terminated at Heroku’s load balancer.
  • Your Strapi dyno receives the request as HTTP, unless you explicitly tell Koa to trust the proxy headers (X-Forwarded-*).
  • koa-session checks ctx.secure before setting Secure cookies.
  • Without proxy trust, ctx.secure is false, so you get the error.

This is especially common with third-party OAuth flows (Google, GitHub, etc.) because they require Secure + SameSite=None cookies to maintain the session across domains.


Step 1 — Configure config/server.ts

Tell Strapi to trust the proxy and set your public HTTPS URL:

export default ({ env }) => ({
  host: env('HOST', '0.0.0.0'),
  port: env.int('PORT', 1337),
  proxy: true, // trust X-Forwarded-* headers
  url: env('URL', 'https://your-app-name.herokuapp.com'),
  app: { keys: env.array('APP_KEYS') },
});

Step 2 — Update config/middlewares.ts

Move the session middleware before security/cors/body and enable proxy mode inside it:

export default [
  'strapi::errors',
  {
    name: 'strapi::session',
    config: {
      key: 'strapi.sid',
      secure: true,         // required with SameSite=None
      sameSite: 'none',     // needed for Google OAuth cross-site cookies
      rolling: false,
      renew: false,
      proxy: true,          // tell koa-session to trust the proxy
    },
  },
  'strapi::security',
  'strapi::cors',
  'strapi::body',
  'strapi::logger',
  'strapi::query',
  'strapi::favicon',
  'strapi::public',
];

Step 3 — Force Proxy Trust in src/index.ts

Some setups still fail because the proxy setting in config/server.ts doesn’t get applied early enough. You can enforce it at runtime:

// src/index.ts
import type { Core } from '@strapi/strapi';

export default {
  register({ strapi }: { strapi: Core.Strapi }) {
    // Ensure Koa trusts proxy headers from Heroku
    strapi.server.app.proxy = true;
  },
  bootstrap() {},
};

Step 4 — (Optional) Debugging the Proxy Headers

If you want to verify what Heroku sends, add a quick middleware:

export default () => {
  return async (ctx, next) => {
    const xfp = ctx.get('x-forwarded-proto');
    console.log('[PROTO DEBUG]', {
      xfp,
      secure: ctx.secure,
      url: ctx.originalUrl,
    });
    await next();
  };
};

When you hit your Google connect URL, you should see:

[PROTO DEBUG] { xfp: 'https', secure: true, url: '/api/connect/google' }

Step 5 — Environment Variables

On Heroku, make sure you have:

URL=https://your-app-name.herokuapp.com

Step 6 — Test the Flow

  1. Redeploy to Heroku.
  2. Open https://your-app-name.herokuapp.com/api/connect/google.
  3. You should be redirected to Google, then back to /api/connect/google/callback without the 500 error.

Conclusion

The error isn’t a bug in Strapi — it’s a mismatch between Heroku’s proxying and Koa’s cookie security checks. By explicitly trusting the proxy in both Strapi and koa-session, you allow ctx.secure to be true when traffic is HTTPS at the load balancer.

With this fix, Secure cookies will work correctly, and your Google OAuth integration will stop breaking.