65

My Node.js app is able to work with local Postgres database via npm pg module. I can connect to the Heroku hosted Postgres database (free Hobby Dev plan) via command line with heroku pg:psql command as well. But when my Node.js app is trying to query to Heroku hosted Postgres database I am receiving an self signed certificate error.

Here is the output with self signed certificate error:

(node:2100) UnhandledPromiseRejectionWarning: Error: self signed certificate
    at TLSSocket.onConnectSecure (_tls_wrap.js:1051:34)
    at TLSSocket.emit (events.js:189:13)
    at TLSSocket._finishInit (_tls_wrap.js:633:8)
(node:2100) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:2100) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
D:\MY\DEV\PROJECTS\AdsSubscribeBot\test.js:57
  if (err) throw err;
           ^

Error: Connection terminated unexpectedly
    at Connection.con.once (D:\MY\DEV\PROJECTS\AdsSubscribeBot\node_modules\pg\lib\client.js:264:9)
    at Object.onceWrapper (events.js:277:13)
    at Connection.emit (events.js:189:13)
    at Socket.<anonymous> (D:\MY\DEV\PROJECTS\AdsSubscribeBot\node_modules\pg\lib\connection.js:76:10)
    at Socket.emit (events.js:194:15)
    at TCP._handle.close (net.js:597:12)

Simpliest way to reproduce this error is to try use the sample code to connecting in Node.js from Heroku devcenter: https://devcenter.heroku.com/articles/heroku-postgresql#connecting-in-node-js

Here is the sample of the code that causes self signed certificate error:

const connectionString = 'postgres://USERNAME:PASSWORD@HOST:PORT/DB_NAME';

const { Client } = require('pg');

const client = new Client({
  connectionString: connectionString,
  ssl: true
});

client.connect();

client.query('SELECT * FROM users;', (err, res) => {
  if (err) throw err;
  for (let row of res.rows) {
    console.log(JSON.stringify(row));
  }
  client.end();
});

Maybe someone has faced the same issue and know the way how to solve it.

Thanks in advance for any help.

2
  • 2
    The only workaround I have found is to set NODE_TLS_REJECT_UNAUTHORIZED=0 in your environment. I won't post it as an answer because it is a hack and insecure, but hopefully someone will post an actual solution at some point. Commented Apr 8, 2020 at 16:47
  • 1
    @RickMogstad Thanks, but yes it is a hack :) But I would like to know the reason. Unfortunately, I can't create a ticket to Heroku support team because I use a free plan... :) So the only option is to ask community. Commented Apr 9, 2020 at 11:46

5 Answers 5

115

Check you pg config. It sounds like you are using pg 8 which deprecates implicit disabling of certificate verification (as you have in your config where ssl is set to true but no ssl configuration is provided). Specify rejectUnauthorized: true to require a valid CA or rejectUnauthorized: false to explicitly opt out of MITM protection.

You can do so where you set up your pg config as follows

const client = new Client({
  connectionString: connectionString,
  ssl: { rejectUnauthorized: false }
})
Sign up to request clarification or add additional context in comments.

5 Comments

I had to do something like this in order to work for both production and my local machine: ssl: this.isProduction() ? { rejectUnauthorized: false } : false
For me, only setting the Environment variable worked: PGSSLMODE=no-verify (see Heroku docs: devcenter.heroku.com/articles/…)
Try using <code> const _connectionDetails = { host: process.env.PG_HOST, user: process.env.PG_USERNAME, password: process.env.PG_PASSWORD, database: process.env.PG_DB, ssl: {rejectUnauthorized: false} } const connection = knex({ client: 'pg', connection: _connectionDetails, debug: false//(process.env.APP_ENV !== 'production') });</code> for Knex pg connection
Should we also set requestCert option, sibling to the rejectUnauthorized, to true as well, since its doc says rejectUnauthorized has an effect only if requestCert is true and requestCert has default value of false.
I find it amazing that pretty much every answer here suggests this insecure hack that opens the developer up to a man-in-the-middle attack.
19

To get this to work, I had to add:

ssl: { rejectUnauthorized: false }

but also add this to the environment:

NODE_TLS_REJECT_UNAUTHORIZED=0

4 Comments

it's bad desicion...
@hdoitc it only worked for me now using this, why is it a bad decision?
@SergioToledoPiza it is insecure, as it is specifying to allow middle man attacks.
This is what I needed on Heroku using NestJS and TypeORM
18

If anyone is still seeing issues with this after appending the SSL object to the Client object and they are using a connection string. Make sure that you don't have an ssl parameter in the connection string. If you are working with Digital Ocean this parameter is included in the generated connection string.

This is how Digital Ocean formats their connection strings by default

postgres://USERNAME:PASSWORD@HOST:PORT/DB_NAME:25060/defaultdb?&sslmode=require

3 Comments

Absolutely required: lost 3 hours of my life before I came across this.
Ah. yes. I was using Heroku. But the same issue existed. I had ?ssl=true tacked on the end. Thanks!
Same issue migrating from Sequelize 6.7.0 to 6.12.0, removing the sslmode parameter solved the issue.
7

Below is a variation of the accepted answer using Knex.js. Tested on Heroku.

const parse = require('pg-connection-string').parse;
const pgconfig = parse('your-pg-connection-string');
pgconfig.ssl = { rejectUnauthorized: false };

const knex = Knex({
  client: 'pg',
  connection: pgconfig,
});

2 Comments

No other methods I've tried works. This is the only one that works for me, but it did bring in yet another dependency... remember to install npm install --save pg-connection-string for this to work.
@nickang - were other methods not working because you had ssl or sslmode in your db connection string? stackoverflow.com/a/69994711/1006183
4

Thanks to @samkhan27. I just added ssl: { rejectUnauthorized: false }

My full code:

const db = new Sequelize(
process.env.DATABASE_URL ||
`postgres://postgres:w2w2@localhost:5432/${databaseName}`, 
{
    logging: false,
    ssl: { rejectUnauthorized: false } //solved the problem with self signed sertificate
}    

)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.