diff --git a/drivers/net/mlx5/mlx5.h b/drivers/net/mlx5/mlx5.h
index 2927b851b..cb25beb3c 100644
--- a/drivers/net/mlx5/mlx5.h
+++ b/drivers/net/mlx5/mlx5.h
@@ -154,6 +154,13 @@ struct priv {
 	struct rte_security_ctx security; /* Security context. */
 };
 
+/* Security session. */
+struct mlx5_security_session {
+	struct rte_security_ipsec_xform ipsec_xform;
+	struct rte_eth_dev *dev;
+	struct ibv_action_xfrm  *ibv_action_xfrm;
+};
+
 /**
  * Lock private structure to protect it from concurrent access in the
  * control path.
diff --git a/drivers/net/mlx5/mlx5_flow.c b/drivers/net/mlx5/mlx5_flow.c
index ff50470b5..704c47820 100644
--- a/drivers/net/mlx5/mlx5_flow.c
+++ b/drivers/net/mlx5/mlx5_flow.c
@@ -78,6 +78,20 @@ ibv_destroy_counter_set(struct ibv_counter_set *cs)
 }
 #endif
 
+#ifndef HAVE_IBV_IPSEC_SUPPORT
+/* Define dummy structure when IPsec is not available in Verbs. */
+
+/* Dummy spec ESP defined when missing in Verbs. */
+struct ibv_flow_spec_esp {
+	int dummy;
+};
+
+/* Dummy transform action defined when missing in Verbs. */
+struct ibv_flow_spec_action_xfrm {
+	int dummy;
+};
+#endif
+
 /* Dev ops structure defined in mlx5.c */
 extern const struct eth_dev_ops mlx5_dev_ops;
 extern const struct eth_dev_ops mlx5_dev_ops_isolate;
@@ -129,6 +143,14 @@ mlx5_flow_create_flag_mark(struct mlx5_flow_parse *parser, uint32_t mark_id);
 static int
 mlx5_flow_create_count(struct priv *priv, struct mlx5_flow_parse *parser);
 
+static int
+mlx5_flow_create_esp(const struct rte_flow_item *item,
+		     const void *default_mask,
+		     void *data);
+
+static void
+mlx5_flow_create_xfrm(struct mlx5_flow_parse *parser);
+
 /* Hash RX queue types. */
 enum hash_rxq_type {
 	HASH_RXQ_TCPV4,
@@ -244,6 +266,8 @@ struct rte_flow {
 	TAILQ_ENTRY(rte_flow) next; /**< Pointer to the next flow structure. */
 	uint32_t mark:1; /**< Set if the flow is marked. */
 	uint32_t drop:1; /**< Drop queue. */
+	uint32_t security:1; /**< Security flow. */
+	uint32_t ingress:1; /**< Ingress flow. */
 	uint16_t queues_n; /**< Number of entries in queue[]. */
 	uint16_t (*queues)[]; /**< Queues indexes to use. */
 	struct rte_eth_rss_conf rss_conf; /**< RSS configuration */
@@ -305,6 +329,7 @@ static const enum rte_flow_action_type valid_actions[] = {
 #ifdef HAVE_IBV_DEVICE_COUNTERS_SET_SUPPORT
 	RTE_FLOW_ACTION_TYPE_COUNT,
 #endif
+	RTE_FLOW_ACTION_TYPE_SECURITY,
 	RTE_FLOW_ACTION_TYPE_END,
 };
 
@@ -343,7 +368,8 @@ static const struct mlx5_flow_items mlx5_flow_items[] = {
 	},
 	[RTE_FLOW_ITEM_TYPE_IPV4] = {
 		.items = ITEMS(RTE_FLOW_ITEM_TYPE_UDP,
-			       RTE_FLOW_ITEM_TYPE_TCP),
+			       RTE_FLOW_ITEM_TYPE_TCP,
+			       RTE_FLOW_ITEM_TYPE_ESP),
 		.actions = valid_actions,
 		.mask = &(const struct rte_flow_item_ipv4){
 			.hdr = {
@@ -360,7 +386,8 @@ static const struct mlx5_flow_items mlx5_flow_items[] = {
 	},
 	[RTE_FLOW_ITEM_TYPE_IPV6] = {
 		.items = ITEMS(RTE_FLOW_ITEM_TYPE_UDP,
-			       RTE_FLOW_ITEM_TYPE_TCP),
+			       RTE_FLOW_ITEM_TYPE_TCP,
+			       RTE_FLOW_ITEM_TYPE_ESP),
 		.actions = valid_actions,
 		.mask = &(const struct rte_flow_item_ipv6){
 			.hdr = {
@@ -424,6 +451,17 @@ static const struct mlx5_flow_items mlx5_flow_items[] = {
 		.convert = mlx5_flow_create_vxlan,
 		.dst_sz = sizeof(struct ibv_flow_spec_tunnel),
 	},
+	[RTE_FLOW_ITEM_TYPE_ESP] = {
+		.actions = valid_actions,
+		.mask = &(const struct rte_flow_item_esp){
+			.hdr = {
+				.spi = 0xffffffff,
+			}
+		},
+		.mask_sz = sizeof(struct rte_flow_item_esp),
+		.convert = mlx5_flow_create_esp,
+		.dst_sz = sizeof(struct ibv_flow_spec_esp),
+	},
 };
 
 /** Structure to pass to the conversion function. */
@@ -434,6 +472,7 @@ struct mlx5_flow_parse {
 	uint32_t drop:1; /**< Target is a drop queue. */
 	uint32_t mark:1; /**< Mark is present in the flow. */
 	uint32_t count:1; /**< Count is present in the flow. */
+	uint32_t ingress:1; /** Flow is for ingress. */
 	uint32_t mark_id; /**< Mark identifier. */
 	uint16_t queues[RTE_MAX_QUEUES_PER_PORT]; /**< Queues indexes to use. */
 	uint16_t queues_n; /**< Number of entries in queue[]. */
@@ -441,6 +480,7 @@ struct mlx5_flow_parse {
 	uint8_t rss_key[40]; /**< copy of the RSS key. */
 	enum hash_rxq_type layer; /**< Last pattern layer detected. */
 	struct ibv_counter_set *cs; /**< Holds the counter set for the rule */
+	const struct mlx5_security_session *security; /**< Security session */
 	struct {
 		struct ibv_flow_attr *ibv_attr;
 		/**< Pointer to Verbs attributes. */
@@ -601,7 +641,6 @@ priv_flow_convert_attributes(struct priv *priv,
 			     struct mlx5_flow_parse *parser)
 {
 	(void)priv;
-	(void)parser;
 	if (attr->group) {
 		rte_flow_error_set(error, ENOTSUP,
 				   RTE_FLOW_ERROR_TYPE_ATTR_GROUP,
@@ -616,20 +655,28 @@ priv_flow_convert_attributes(struct priv *priv,
 				   "priorities are not supported");
 		return -rte_errno;
 	}
-	if (attr->egress) {
+	if (attr->egress && attr->ingress) {
 		rte_flow_error_set(error, ENOTSUP,
 				   RTE_FLOW_ERROR_TYPE_ATTR_EGRESS,
 				   NULL,
-				   "egress is not supported");
+				   "egress with ingress is not supported");
 		return -rte_errno;
 	}
-	if (!attr->ingress) {
+	if (attr->ingress && attr->egress) {
 		rte_flow_error_set(error, ENOTSUP,
 				   RTE_FLOW_ERROR_TYPE_ATTR_INGRESS,
 				   NULL,
-				   "only ingress is supported");
+				   "ingress with egress is supported");
+		return -rte_errno;
+	}
+	if (!(attr->ingress ^ attr->egress)) {
+		rte_flow_error_set(error, ENOTSUP,
+				   RTE_FLOW_ERROR_TYPE_ATTR,
+				   NULL,
+				   "Missing Ingress of Egress");
 		return -rte_errno;
 	}
+	parser->ingress = attr->ingress;
 	return 0;
 }
 
@@ -649,10 +696,10 @@ priv_flow_convert_attributes(struct priv *priv,
  *   0 on success, a negative errno value otherwise and rte_errno is set.
  */
 static int
-priv_flow_convert_actions(struct priv *priv,
-			  const struct rte_flow_action actions[],
-			  struct rte_flow_error *error,
-			  struct mlx5_flow_parse *parser)
+priv_flow_convert_actions_ingress(struct priv *priv,
+				  const struct rte_flow_action actions[],
+				  struct rte_flow_error *error,
+				  struct mlx5_flow_parse *parser)
 {
 	int ret = 0;
 	const char *msg = NULL;
@@ -760,6 +807,27 @@ priv_flow_convert_actions(struct priv *priv,
 			}
 			parser->mark = 1;
 			parser->mark_id = mark->id;
+		} else if (actions->type == RTE_FLOW_ACTION_TYPE_SECURITY) {
+			if (!actions->conf) {
+				ret = EINVAL;
+				msg = "invalid security configuration";
+				action = actions;
+				goto error;
+			}
+			parser->security =
+				get_sec_session_private_data(actions->conf);
+			if (!parser->security) {
+				ret = EINVAL;
+				msg = "invalid security configuration";
+				action = actions;
+				goto error;
+			}
+			if (!priv->ipsec_en) {
+				ret = ENOTSUP;
+				msg = "action not supported";
+				action = actions;
+				goto error;
+			}
 		} else if (actions->type == RTE_FLOW_ACTION_TYPE_FLAG) {
 			parser->mark = 1;
 		} else if (actions->type == RTE_FLOW_ACTION_TYPE_COUNT &&
@@ -785,6 +853,84 @@ priv_flow_convert_actions(struct priv *priv,
 }
 
 /**
+ * Extract actions request to the parser.
+ *
+ * @param priv
+ *   Pointer to private structure.
+ * @param[in] actions
+ *   Associated actions (list terminated by the END action).
+ * @param[out] error
+ *   Perform verbose error reporting if not NULL.
+ * @param[in, out] parser
+ *   Internal parser structure.
+ *
+ * @return
+ *   0 on success, a negative errno value otherwise and rte_errno is set.
+ */
+static int
+priv_flow_convert_actions_egress(struct priv *priv,
+				 const struct rte_flow_action actions[],
+				 struct rte_flow_error *error,
+				 struct mlx5_flow_parse *parser)
+{
+	int ret;
+	const char *msg;
+	const struct rte_flow_action *action;
+
+	/*
+	 * Add default RSS configuration necessary for Verbs to create QP even
+	 * if no RSS is necessary.
+	 */
+	priv_flow_convert_rss_conf(priv, parser,
+				   (const struct rte_eth_rss_conf *)
+				   &priv->rss_conf);
+	for (; actions->type != RTE_FLOW_ACTION_TYPE_END; ++actions) {
+		if (actions->type == RTE_FLOW_ACTION_TYPE_VOID) {
+			continue;
+		} else if (actions->type == RTE_FLOW_ACTION_TYPE_SECURITY) {
+			if (!actions->conf) {
+				ret = EINVAL;
+				msg = "invalid security configuration";
+				action = actions;
+				goto error;
+			}
+			parser->security =
+				get_sec_session_private_data(actions->conf);
+			if (!parser->security) {
+				ret = EINVAL;
+				msg = "invalid security configuration";
+				action = actions;
+				goto error;
+			}
+			if (!priv->ipsec_en) {
+				ret = ENOTSUP;
+				msg = "action not supported";
+				action = actions;
+				goto error;
+			}
+		} else {
+			ret = ENOTSUP;
+			msg = "action not supported";
+			action = actions;
+			goto error;
+		}
+	}
+	if (parser->drop) {
+		ret = EINVAL;
+		msg = "dropping in egress is not invalid";
+		action = NULL;
+		goto error;
+	}
+	return 0;
+error:
+	rte_flow_error_set(error, ret,
+			   action ? RTE_FLOW_ERROR_TYPE_ACTION :
+			   RTE_FLOW_ERROR_TYPE_HANDLE,
+			   action, msg);
+	return -rte_errno;
+}
+
+/**
  * Validate items.
  *
  * @param priv
@@ -847,7 +993,7 @@ priv_flow_convert_items_validate(struct priv *priv,
 			}
 			parser->inner = IBV_FLOW_SPEC_INNER;
 		}
-		if (parser->drop || parser->queues_n == 1) {
+		if (parser->drop || !parser->ingress || parser->queues_n == 1) {
 			parser->queue[HASH_RXQ_ETH].offset += cur_item->dst_sz;
 		} else {
 			for (n = 0; n != hash_rxq_init_n; ++n)
@@ -865,6 +1011,16 @@ priv_flow_convert_items_validate(struct priv *priv,
 		for (i = 0; i != hash_rxq_init_n; ++i)
 			parser->queue[i].offset += size;
 	}
+	if (parser->security) {
+		unsigned int size = sizeof(struct ibv_flow_spec_action_xfrm);
+
+		if (parser->drop || !parser->ingress) {
+			parser->queue[HASH_RXQ_ETH].offset += size;
+		} else {
+			for (i = 0; i != hash_rxq_init_n; ++i)
+				parser->queue[i].offset += size;
+		}
+	}
 	return 0;
 exit_item_not_supported:
 	rte_flow_error_set(error, ENOTSUP, RTE_FLOW_ERROR_TYPE_ITEM,
@@ -1064,7 +1220,12 @@ priv_flow_convert(struct priv *priv,
 	ret = priv_flow_convert_attributes(priv, attr, error, parser);
 	if (ret)
 		return ret;
-	ret = priv_flow_convert_actions(priv, actions, error, parser);
+	if (attr->ingress)
+		ret = priv_flow_convert_actions_ingress(priv, actions, error,
+							parser);
+	else
+		ret = priv_flow_convert_actions_egress(priv, actions, error,
+						       parser);
 	if (ret)
 		return ret;
 	ret = priv_flow_convert_items_validate(priv, items, error, parser);
@@ -1075,7 +1236,7 @@ priv_flow_convert(struct priv *priv,
 	 * Second step.
 	 * Allocate the memory space to store verbs specifications.
 	 */
-	if (parser->drop || parser->queues_n == 1) {
+	if (parser->drop || parser->queues_n == 1 || !parser->ingress) {
 		unsigned int priority =
 			attr->priority +
 			hash_rxq_init[HASH_RXQ_ETH].flow_priority;
@@ -1133,6 +1294,8 @@ priv_flow_convert(struct priv *priv,
 		if (!parser->cs)
 			goto exit_count_error;
 	}
+	if (parser->security)
+		mlx5_flow_create_xfrm(parser);
 	/*
 	 * Last step. Complete missing specification to reach the RSS
 	 * configuration.
@@ -1611,6 +1774,73 @@ mlx5_flow_create_count(struct priv *priv __rte_unused,
 }
 
 /**
+ * Convert ESP Item to Verbs specification.
+ *
+ * @param item[in]
+ *   Item specification.
+ * @param default_mask[in]
+ *   Default bit-masks to use when item->mask is not provided.
+ * @param data[in, out]
+ *   User structure.
+ *
+ * @return
+ *   0 on success, errno value on failure.
+ */
+static int
+mlx5_flow_create_esp(const struct rte_flow_item *item __rte_unused,
+		     const void *default_mask __rte_unused,
+		     void *data __rte_unused)
+{
+	int ret = ENOTSUP;
+#ifdef HAVE_IBV_IPSEC_SUPPORT
+	const struct rte_flow_item_esp *spec = item->spec;
+	const struct rte_flow_item_esp *mask = item->mask;
+	struct mlx5_flow_parse *parser = (struct mlx5_flow_parse *)data;
+	unsigned int size = sizeof(struct ibv_flow_spec_esp);
+	struct ibv_flow_spec_esp esp = {
+		.type = parser->inner | IBV_FLOW_SPEC_ESP,
+		.size = size,
+	};
+
+	ret = 0;
+	if (spec) {
+		if (!mask)
+			mask = default_mask;
+		esp.val.spi = htonl(spec->hdr.spi);
+		esp.val.seq = htonl(spec->hdr.seq);
+		esp.mask.spi = htonl(mask->hdr.spi);
+		esp.mask.seq = htonl(mask->hdr.seq);
+		esp.val.spi &= esp.mask.spi;
+		esp.val.seq &= esp.mask.seq;
+	}
+	mlx5_flow_create_copy(parser, &esp, size);
+#endif
+	return ret;
+}
+
+/**
+ * Convert XFRM action to Verbs specification.
+ *
+ * @param parser
+ *   Pointer to MLX5 flow parser structure.
+ */
+static void
+mlx5_flow_create_xfrm(struct mlx5_flow_parse *parser __rte_unused)
+{
+#ifdef HAVE_IBV_IPSEC_SUPPORT
+	unsigned int size = sizeof(struct ibv_flow_spec_action_xfrm);
+	struct ibv_flow_spec_action_xfrm xfrm_spec = {
+		.type = IBV_FLOW_SPEC_ACTION_XFRM,
+		.action = parser->security->ibv_action_xfrm,
+		.size = size,
+	};
+
+	assert(parser->security);
+	mlx5_flow_create_copy(parser, &xfrm_spec, size);
+#endif
+}
+
+/**
  * Complete flow rule creation with a drop queue.
  *
  * @param priv
@@ -1797,6 +2027,7 @@ priv_flow_create_action_queue(struct priv *priv,
 			(*priv->rxqs)[parser->queues[i]];
 
 		q->mark |= parser->mark;
+		q->ipsec_en |= !!(parser->security);
 	}
 	return 0;
 error:
@@ -1870,18 +2101,23 @@ priv_flow_create(struct priv *priv,
 	memcpy(flow->queues, parser.queues, parser.queues_n * sizeof(uint16_t));
 	flow->queues_n = parser.queues_n;
 	flow->mark = parser.mark;
+	flow->security = !!parser.security;
 	/* Copy RSS configuration. */
 	flow->rss_conf = parser.rss_conf;
 	flow->rss_conf.rss_key = flow->rss_key;
 	memcpy(flow->rss_key, parser.rss_key, parser.rss_conf.rss_key_len);
 	/* finalise the flow. */
-	if (parser.drop)
-		err = priv_flow_create_action_queue_drop(priv, &parser, flow,
-							 error);
-	else
-		err = priv_flow_create_action_queue(priv, &parser, flow, error);
-	if (err)
-		goto exit;
+	flow->ingress = parser.ingress;
+	if (parser.ingress) {
+		if (parser.drop)
+			err = priv_flow_create_action_queue_drop(priv, &parser,
+								 flow, error);
+		else
+			err = priv_flow_create_action_queue(priv, &parser, flow,
+							    error);
+		if (err)
+			goto exit;
+	}
 	TAILQ_INSERT_TAIL(list, flow, next);
 	DEBUG("Flow created %p", (void *)flow);
 	return flow;
@@ -1962,17 +2198,19 @@ priv_flow_destroy(struct priv *priv,
 	for (i = 0; i != flow->queues_n; ++i) {
 		struct rte_flow *tmp;
 		int mark = 0;
+		int ipsec = 0;
 
 		/*
 		 * To remove the mark from the queue, the queue must not be
 		 * present in any other marked flow (RSS or not).
+		 * Same thing for the IPsec.
 		 */
 		TAILQ_FOREACH(tmp, list, next) {
 			unsigned int j;
 			uint16_t *tqs = NULL;
 			uint16_t tq_n = 0;
 
-			if (!tmp->mark)
+			if (!tmp->mark && !tmp->security)
 				continue;
 			for (j = 0; j != hash_rxq_init_n; ++j) {
 				if (!tmp->frxq[j].hrxq)
@@ -1982,11 +2220,17 @@ priv_flow_destroy(struct priv *priv,
 			}
 			if (!tq_n)
 				continue;
-			for (j = 0; (j != tq_n) && !mark; j++)
-				if (tqs[j] == (*flow->queues)[i])
-					mark = 1;
+			for (j = 0; (j != tq_n) && !mark; j++) {
+				if (tqs[j] == (*flow->queues)[i]) {
+					mark |= (*priv->rxqs)
+						[(*flow->queues)[j]]->mark;
+					ipsec |= !!(*priv->rxqs)
+						[(*flow->queues)[j]]->ipsec_en;
+				}
+			}
 		}
 		(*priv->rxqs)[(*flow->queues)[i]]->mark = mark;
+		(*priv->rxqs)[(*flow->queues)[i]]->ipsec_en = ipsec;
 	}
 free:
 	if (flow->drop) {
@@ -2170,7 +2414,7 @@ priv_flow_stop(struct priv *priv, struct mlx5_flows *list)
 			/* Next flow. */
 			continue;
 		}
-		if (flow->mark) {
+		if (flow->mark || flow->security) {
 			struct mlx5_ind_table_ibv *ind_tbl = NULL;
 
 			for (i = 0; i != hash_rxq_init_n; ++i) {
@@ -2179,8 +2423,10 @@ priv_flow_stop(struct priv *priv, struct mlx5_flows *list)
 				ind_tbl = flow->frxq[i].hrxq->ind_table;
 			}
 			assert(ind_tbl);
-			for (i = 0; i != ind_tbl->queues_n; ++i)
+			for (i = 0; i != ind_tbl->queues_n; ++i) {
 				(*priv->rxqs)[ind_tbl->queues[i]]->mark = 0;
+				(*priv->rxqs)[ind_tbl->queues[i]]->ipsec_en = 0;
+			}
 		}
 		for (i = 0; i != hash_rxq_init_n; ++i) {
 			if (!flow->frxq[i].ibv_flow)
@@ -2263,10 +2509,13 @@ priv_flow_start(struct priv *priv, struct mlx5_flows *list)
 			}
 			DEBUG("Flow %p applied", (void *)flow);
 		}
-		if (!flow->mark)
+		if (!flow->mark && !flow->security)
 			continue;
-		for (i = 0; i != flow->queues_n; ++i)
-			(*priv->rxqs)[(*flow->queues)[i]]->mark = 1;
+		for (i = 0; i != flow->queues_n; ++i) {
+			(*priv->rxqs)[(*flow->queues)[i]]->mark = flow->mark;
+			(*priv->rxqs)[(*flow->queues)[i]]->ipsec_en =
+				flow->security;
+		}
 	}
 	return 0;
 }
diff --git a/drivers/net/mlx5/mlx5_ipsec.c b/drivers/net/mlx5/mlx5_ipsec.c
index 52a3add7a..e7086adbc 100644
--- a/drivers/net/mlx5/mlx5_ipsec.c
+++ b/drivers/net/mlx5/mlx5_ipsec.c
@@ -57,6 +57,9 @@
 		      " please contact Mellanox")
 
 /* Extra verifications, this API is not unstreamed yet. */
+MLX5_IPSEC_SUPPORT_ERROR(IBV_FLOW_ATTR_FLAGS_EGRESS == 1 << 2);
+MLX5_IPSEC_SUPPORT_ERROR(IBV_FLOW_SPEC_ESP == 0x34);
+MLX5_IPSEC_SUPPORT_ERROR(IBV_FLOW_SPEC_ACTION_XFRM == 0x1002);
 MLX5_IPSEC_SUPPORT_ERROR(MLX5DV_CONTEXT_XFRM_FLAGS_ESP_AES_GCM_REQ_METADATA ==
 			 1u << 0);
 MLX5_IPSEC_SUPPORT_ERROR(MLX5DV_CONTEXT_XFRM_FLAGS_ESP_AES_GCM_RX == 1u << 1);
@@ -65,13 +68,6 @@ MLX5_IPSEC_SUPPORT_ERROR(MLX5DV_CONTEXT_XFRM_FLAGS_ESP_AES_GCM_SPI_RSS_ONLY ==
 			 1u << 3);
 #endif
 
-/* Security session. */
-struct mlx5_security_session {
-	struct rte_security_ipsec_xform ipsec_xform;
-	struct rte_eth_dev *dev;
-	struct ibv_action_xfrm  *ibv_action_xfrm;
-};
-
 /** MLX5 Crypto capabilities. */
 struct rte_cryptodev_capabilities mlx5_crypto_capabilities[] = {
 	/* AES GCM (128-bit) */
