diff --git a/app/test-pmd/cmdline_flow.c b/app/test-pmd/cmdline_flow.c
index 00bac0a60d..5771281125 100644
--- a/app/test-pmd/cmdline_flow.c
+++ b/app/test-pmd/cmdline_flow.c
@@ -132,6 +132,7 @@ enum index {
 	/* Queue arguments. */
 	QUEUE_CREATE,
 	QUEUE_DESTROY,
+	QUEUE_UPDATE,
 	QUEUE_AGED,
 	QUEUE_INDIRECT_ACTION,
 
@@ -146,6 +147,9 @@ enum index {
 	QUEUE_DESTROY_ID,
 	QUEUE_DESTROY_POSTPONE,
 
+	/* Queue update arguments. */
+	QUEUE_UPDATE_ID,
+
 	/* Queue indirect action arguments */
 	QUEUE_INDIRECT_ACTION_CREATE,
 	QUEUE_INDIRECT_ACTION_LIST_CREATE,
@@ -1312,6 +1316,7 @@ static const enum index next_table_destroy_attr[] = {
 static const enum index next_queue_subcmd[] = {
 	QUEUE_CREATE,
 	QUEUE_DESTROY,
+	QUEUE_UPDATE,
 	QUEUE_AGED,
 	QUEUE_INDIRECT_ACTION,
 	ZERO,
@@ -3408,6 +3413,14 @@ static const struct token token_list[] = {
 		.args = ARGS(ARGS_ENTRY(struct buffer, queue)),
 		.call = parse_qo_destroy,
 	},
+	[QUEUE_UPDATE] = {
+		.name = "update",
+		.help = "update a flow rule",
+		.next = NEXT(NEXT_ENTRY(QUEUE_UPDATE_ID),
+			     NEXT_ENTRY(COMMON_QUEUE_ID)),
+		.args = ARGS(ARGS_ENTRY(struct buffer, queue)),
+		.call = parse_qo,
+	},
 	[QUEUE_AGED] = {
 		.name = "aged",
 		.help = "list and destroy aged flows",
@@ -3484,6 +3497,15 @@ static const struct token token_list[] = {
 					    args.destroy.rule)),
 		.call = parse_qo_destroy,
 	},
+	[QUEUE_UPDATE_ID] = {
+		.name = "rule",
+		.help = "specify rule id to update",
+		.next = NEXT(NEXT_ENTRY(QUEUE_ACTIONS_TEMPLATE),
+			NEXT_ENTRY(COMMON_UNSIGNED)),
+		.args = ARGS(ARGS_ENTRY(struct buffer,
+				     args.vc.rule_id)),
+		.call = parse_qo,
+	},
 	/* Queue indirect action arguments */
 	[QUEUE_INDIRECT_ACTION_CREATE] = {
 		.name = "create",
@@ -10207,6 +10229,7 @@ parse_qo(struct context *ctx, const struct token *token,
 	}
 	switch (ctx->curr) {
 	case QUEUE_CREATE:
+	case QUEUE_UPDATE:
 		out->command = ctx->curr;
 		ctx->objdata = 0;
 		ctx->object = out;
@@ -10218,6 +10241,7 @@ parse_qo(struct context *ctx, const struct token *token,
 	case QUEUE_ACTIONS_TEMPLATE:
 	case QUEUE_CREATE_POSTPONE:
 	case QUEUE_RULE_ID:
+	case QUEUE_UPDATE_ID:
 		return len;
 	case ITEM_PATTERN:
 		out->args.vc.pattern =
@@ -12233,6 +12257,11 @@ cmd_flow_parsed(const struct buffer *in)
 					in->args.destroy.rule_n,
 					in->args.destroy.rule);
 		break;
+	case QUEUE_UPDATE:
+		port_queue_flow_update(in->port, in->queue, in->postpone,
+				in->args.vc.rule_id, in->args.vc.act_templ_id,
+				in->args.vc.actions);
+		break;
 	case PUSH:
 		port_queue_flow_push(in->port, in->queue);
 		break;
diff --git a/app/test-pmd/config.c b/app/test-pmd/config.c
index 2cf01d09b3..d9437bd164 100644
--- a/app/test-pmd/config.c
+++ b/app/test-pmd/config.c
@@ -2799,6 +2799,7 @@ port_queue_flow_create(portid_t port_id, queueid_t queue_id,
 
 	pf->next = port->flow_list;
 	pf->id = id;
+	pf->table = pt;
 	pf->flow = flow;
 	job->pf = pf;
 	port->flow_list = pf;
@@ -2905,6 +2906,94 @@ queue_action_list_handle_create(portid_t port_id, uint32_t queue_id,
 		 job, error);
 }
 
+/** Enqueue update flow rule operation. */
+int
+port_queue_flow_update(portid_t port_id, queueid_t queue_id,
+		       bool postpone, uint32_t rule_idx, uint32_t actions_idx,
+		       const struct rte_flow_action *actions)
+{
+	struct rte_flow_op_attr op_attr = { .postpone = postpone };
+	struct rte_port *port;
+	struct port_flow *pf, *uf;
+	struct port_flow **tmp;
+	struct port_table *pt;
+	bool found;
+	struct rte_flow_error error = { RTE_FLOW_ERROR_TYPE_NONE, NULL, NULL };
+	struct rte_flow_action_age *age = age_action_get(actions);
+	struct queue_job *job;
+
+	if (port_id_is_invalid(port_id, ENABLED_WARN) ||
+	    port_id == (portid_t)RTE_PORT_ALL)
+		return -EINVAL;
+	port = &ports[port_id];
+
+	if (queue_id >= port->queue_nb) {
+		printf("Queue #%u is invalid\n", queue_id);
+		return -EINVAL;
+	}
+
+	found = false;
+	tmp = &port->flow_list;
+	while (*tmp) {
+		pf = *tmp;
+		if (rule_idx == pf->id) {
+			found = true;
+			break;
+		}
+		tmp = &(*tmp)->next;
+	}
+	if (!found) {
+		printf("Flow rule #%u is invalid\n", rule_idx);
+		return -EINVAL;
+	}
+
+	pt = pf->table;
+	if (actions_idx >= pt->nb_actions_templates) {
+		printf("Actions template index #%u is invalid,"
+		       " %u templates present in the table\n",
+		       actions_idx, pt->nb_actions_templates);
+		return -EINVAL;
+	}
+
+	job = calloc(1, sizeof(*job));
+	if (!job) {
+		printf("Queue flow create job allocate failed\n");
+		return -ENOMEM;
+	}
+	job->type = QUEUE_JOB_TYPE_FLOW_UPDATE;
+
+	uf = port_flow_new(&pt->flow_attr, pf->rule.pattern_ro, actions, &error);
+	if (!uf) {
+		free(job);
+		return port_flow_complain(&error);
+	}
+
+	if (age) {
+		uf->age_type = ACTION_AGE_CONTEXT_TYPE_FLOW;
+		age->context = &uf->age_type;
+	}
+
+	/*
+	 * Poisoning to make sure PMD update it in case of error.
+	 */
+	memset(&error, 0x44, sizeof(error));
+	if (rte_flow_async_actions_update(port_id, queue_id, &op_attr, pf->flow,
+					  actions, actions_idx, job, &error)) {
+		free(uf);
+		free(job);
+		return port_flow_complain(&error);
+	}
+	uf->next = pf->next;
+	uf->id = pf->id;
+	uf->table = pt;
+	uf->flow = pf->flow;
+	*tmp = uf;
+	job->pf = pf;
+
+	printf("Flow rule #%u update enqueued\n", pf->id);
+	return 0;
+}
+
 /** Enqueue indirect action create operation. */
 int
 port_queue_action_handle_create(portid_t port_id, uint32_t queue_id,
@@ -3394,7 +3483,8 @@ port_queue_flow_pull(portid_t port_id, queueid_t queue_id)
 		if (res[i].status == RTE_FLOW_OP_SUCCESS)
 			success++;
 		job = (struct queue_job *)res[i].user_data;
-		if (job->type == QUEUE_JOB_TYPE_FLOW_DESTROY)
+		if (job->type == QUEUE_JOB_TYPE_FLOW_DESTROY ||
+		    job->type == QUEUE_JOB_TYPE_FLOW_UPDATE)
 			free(job->pf);
 		else if (job->type == QUEUE_JOB_TYPE_ACTION_DESTROY)
 			free(job->pia);
diff --git a/app/test-pmd/testpmd.h b/app/test-pmd/testpmd.h
index 5eeb50f67c..1761768add 100644
--- a/app/test-pmd/testpmd.h
+++ b/app/test-pmd/testpmd.h
@@ -110,6 +110,7 @@ enum {
 enum {
 	QUEUE_JOB_TYPE_FLOW_CREATE,
 	QUEUE_JOB_TYPE_FLOW_DESTROY,
+	QUEUE_JOB_TYPE_FLOW_UPDATE,
 	QUEUE_JOB_TYPE_ACTION_CREATE,
 	QUEUE_JOB_TYPE_ACTION_DESTROY,
 	QUEUE_JOB_TYPE_ACTION_UPDATE,
@@ -225,6 +226,7 @@ struct port_flow {
 	struct port_flow *next; /**< Next flow in list. */
 	struct port_flow *tmp; /**< Temporary linking. */
 	uint32_t id; /**< Flow rule ID. */
+	struct port_table *table; /**< Flow table. */
 	struct rte_flow *flow; /**< Opaque flow object returned by PMD. */
 	struct rte_flow_conv_rule rule; /**< Saved flow rule description. */
 	enum age_action_context_type age_type; /**< Age action context type. */
@@ -983,6 +985,9 @@ int port_queue_flow_create(portid_t port_id, queueid_t queue_id,
 			   const struct rte_flow_action *actions);
 int port_queue_flow_destroy(portid_t port_id, queueid_t queue_id,
 			    bool postpone, uint32_t n, const uint32_t *rule);
+int port_queue_flow_update(portid_t port_id, queueid_t queue_id,
+			   bool postpone, uint32_t rule_idx, uint32_t actions_idx,
+			   const struct rte_flow_action *actions);
 int port_queue_action_handle_create(portid_t port_id, uint32_t queue_id,
 			bool postpone, uint32_t id,
 			const struct rte_flow_indir_action_conf *conf,
