diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index f6bf32ef2d0..fc2a88fc3d7 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -5013,6 +5013,45 @@ SELECT ft1.c1 FROM ft1 JOIN ft2 on ft1.c1 = ft2.c1 WHERE
                Remote SQL: SELECT r5."C 1", r6.c1 FROM ("S 1"."T 1" r5 INNER JOIN "S 1"."T 3" r6 ON (((r5."C 1" = r6.c1)))) ORDER BY r5."C 1" ASC NULLS LAST
 (13 rows)
 
+-- Can't push down semi-join with a non-shippable join clause, as evaluating
+-- it locally would require inner rel vars in the remote targetlist
+CREATE FUNCTION f_noship(int) RETURNS int LANGUAGE plpgsql IMMUTABLE
+  AS $$ BEGIN RETURN $1; END $$;
+SET enable_hashjoin TO off;
+SET enable_mergejoin TO off;
+SET enable_hashagg TO off;
+SET enable_sort TO off;
+EXPLAIN (verbose, costs off)
+SELECT count(*) FROM ft2
+  WHERE EXISTS (SELECT 1 FROM ft4 WHERE ft4.c1 = f_noship(ft2.c1));
+                         QUERY PLAN                         
+------------------------------------------------------------
+ Aggregate
+   Output: count(*)
+   ->  Nested Loop Semi Join
+         Join Filter: (f_noship(ft2.c1) = ft4.c1)
+         ->  Foreign Scan on public.ft2
+               Output: ft2.c1
+               Remote SQL: SELECT "C 1" FROM "S 1"."T 1"
+         ->  Materialize
+               Output: ft4.c1
+               ->  Foreign Scan on public.ft4
+                     Output: ft4.c1
+                     Remote SQL: SELECT c1 FROM "S 1"."T 3"
+(12 rows)
+
+SELECT count(*) FROM ft2
+  WHERE EXISTS (SELECT 1 FROM ft4 WHERE ft4.c1 = f_noship(ft2.c1));
+ count 
+-------
+    50
+(1 row)
+
+RESET enable_hashjoin;
+RESET enable_mergejoin;
+RESET enable_hashagg;
+RESET enable_sort;
+DROP FUNCTION f_noship(int);
 -- ===================================================================
 -- test writable foreign table stuff
 -- ===================================================================
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 2ad734c8a8a..dadc761a4a8 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -5739,21 +5739,32 @@ postgresImportForeignSchema(ImportForeignSchemaStmt *stmt, Oid serverOid)
 }
 
 /*
- * Check if reltarget is safe enough to push down semi-join.  Reltarget is not
- * safe, if it contains references to inner rel relids, which do not belong to
- * outer rel.
+ * Check if it's safe to push down a semi-join.  We deparse a semi-join as an
+ * EXISTS() subquery, so the remote targetlist, which consists of the
+ * joinrel's reltarget plus the vars needed by local conditions (see
+ * build_tlist_to_deparse()), mustn't reference the inner rel.
  */
 static bool
-semijoin_target_ok(PlannerInfo *root, RelOptInfo *joinrel, RelOptInfo *outerrel, RelOptInfo *innerrel)
+semijoin_target_ok(PlannerInfo *root, RelOptInfo *joinrel,
+				   RelOptInfo *outerrel, RelOptInfo *innerrel,
+				   List *local_conds)
 {
 	List	   *vars;
 	ListCell   *lc;
-	bool		ok = true;
 
 	Assert(joinrel->reltarget);
 
 	vars = pull_var_clause((Node *) joinrel->reltarget->exprs, PVC_INCLUDE_PLACEHOLDERS);
 
+	foreach(lc, local_conds)
+	{
+		RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc);
+
+		vars = list_concat(vars,
+						   pull_var_clause((Node *) rinfo->clause,
+										   PVC_INCLUDE_PLACEHOLDERS));
+	}
+
 	foreach(lc, vars)
 	{
 		Var		   *var = (Var *) lfirst(lc);
@@ -5763,18 +5774,11 @@ semijoin_target_ok(PlannerInfo *root, RelOptInfo *joinrel, RelOptInfo *outerrel,
 
 		if (bms_is_member(var->varno, innerrel->relids))
 		{
-			/*
-			 * The planner can create semi-join, which refers to inner rel
-			 * vars in its target list. However, we deparse semi-join as an
-			 * exists() subquery, so can't handle references to inner rel in
-			 * the target list.
-			 */
 			Assert(!bms_is_member(var->varno, outerrel->relids));
-			ok = false;
-			break;
+			return false;
 		}
 	}
-	return ok;
+	return true;
 }
 
 /*
@@ -5803,12 +5807,6 @@ foreign_join_ok(PlannerInfo *root, RelOptInfo *joinrel, JoinType jointype,
 		jointype != JOIN_SEMI)
 		return false;
 
-	/*
-	 * We can't push down semi-join if its reltarget is not safe
-	 */
-	if ((jointype == JOIN_SEMI) && !semijoin_target_ok(root, joinrel, outerrel, innerrel))
-		return false;
-
 	/*
 	 * If either of the joining relations is marked as unsafe to pushdown, the
 	 * join can not be pushed down.
@@ -5875,6 +5873,15 @@ foreign_join_ok(PlannerInfo *root, RelOptInfo *joinrel, JoinType jointype,
 		}
 	}
 
+	/*
+	 * We can't push down a semi-join if its remote targetlist would reference
+	 * the inner rel.
+	 */
+	if (jointype == JOIN_SEMI &&
+		!semijoin_target_ok(root, joinrel, outerrel, innerrel,
+							fpinfo->local_conds))
+		return false;
+
 	/*
 	 * deparseExplicitTargetList() isn't smart enough to handle anything other
 	 * than a Var.  In particular, if there's some PlaceHolderVar that would
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index ff39e654df8..79c501579ee 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -1488,6 +1488,25 @@ SELECT ft1.c1 FROM ft1 JOIN ft2 on ft1.c1 = ft2.c1 WHERE
 		SELECT ft2.c1 FROM ft2 JOIN ft4 ON ft2.c1 = ft4.c1)
 	ORDER BY ft1.c1 LIMIT 5;
 
+-- Can't push down semi-join with a non-shippable join clause, as evaluating
+-- it locally would require inner rel vars in the remote targetlist
+CREATE FUNCTION f_noship(int) RETURNS int LANGUAGE plpgsql IMMUTABLE
+  AS $$ BEGIN RETURN $1; END $$;
+SET enable_hashjoin TO off;
+SET enable_mergejoin TO off;
+SET enable_hashagg TO off;
+SET enable_sort TO off;
+EXPLAIN (verbose, costs off)
+SELECT count(*) FROM ft2
+  WHERE EXISTS (SELECT 1 FROM ft4 WHERE ft4.c1 = f_noship(ft2.c1));
+SELECT count(*) FROM ft2
+  WHERE EXISTS (SELECT 1 FROM ft4 WHERE ft4.c1 = f_noship(ft2.c1));
+RESET enable_hashjoin;
+RESET enable_mergejoin;
+RESET enable_hashagg;
+RESET enable_sort;
+DROP FUNCTION f_noship(int);
+
 -- ===================================================================
 -- test writable foreign table stuff
 -- ===================================================================
