ON CONFLICT on two columns where one can be null
Because PostgreSQL can not infer it from the values, you need the index_predicate


  index_predicate
  Used to allow inference of partial unique indexes. Any indexes that satisfy the predicate (which need not actually be partial indexes) can be inferred. Follows CREATE INDEX format. SELECT privilege on any column appearing within index_predicate is required.


Sample Data

CREATE TABLE tbl( col1 int, col2 int, col3 boolean);

CREATE UNIQUE INDEX ON tbl (col1, col2)
WHERE col2 IS NOT NULL; -- NOT NULL

CREATE UNIQUE INDEX ON tbl (col1, col2)
WHERE col2 IS NULL;     -- NULL

INSERT INTO tbl(col1, col2)
VALUES (1, 3, false); -- set value to false


Test

INSERT INTO tbl(col1, col2)
VALUES (1, 3)
ON CONFLICT(col1, col2)
WHERE col2 IS NOT NULL  -- predicate here
DO UPDATE SET col3 = true;

TABLE tbl;

 col1 | col2 | col3 
------+------+------
    1 |    3 | t
(1 row)