diff --git a/include/ovn/actions.h b/include/ovn/actions.h
index 4e2f4d28d..f4997e9c9 100644
--- a/include/ovn/actions.h
+++ b/include/ovn/actions.h
@@ -225,7 +225,11 @@ struct ovnact_ct_commit {
 /* OVNACT_CT_DNAT, OVNACT_CT_SNAT. */
 struct ovnact_ct_nat {
     struct ovnact ovnact;
-    ovs_be32 ip;
+    int family;
+    union {
+        struct in6_addr ipv6;
+        ovs_be32 ipv4;
+    };
     uint8_t ltable;             /* Logical table ID of next table. */
 };
 
diff --git a/lib/actions.c b/lib/actions.c
index c8c9cc5fd..a999a4fda 100644
--- a/lib/actions.c
+++ b/lib/actions.c
@@ -755,11 +755,18 @@ parse_ct_nat(struct action_context *ctx, const char *name,
 
     if (lexer_match(ctx->lexer, LEX_T_LPAREN)) {
         if (ctx->lexer->token.type != LEX_T_INTEGER
-            || ctx->lexer->token.format != LEX_F_IPV4) {
-            lexer_syntax_error(ctx->lexer, "expecting IPv4 address");
+            || (ctx->lexer->token.format != LEX_F_IPV4
+                && ctx->lexer->token.format != LEX_F_IPV6)) {
+            lexer_syntax_error(ctx->lexer, "expecting IPv4 or IPv6 address");
             return;
         }
-        cn->ip = ctx->lexer->token.value.ipv4;
+        if (ctx->lexer->token.format == LEX_F_IPV4) {
+            cn->family = AF_INET;
+            cn->ipv4 = ctx->lexer->token.value.ipv4;
+        } else if (ctx->lexer->token.format == LEX_F_IPV6) {
+            cn->family = AF_INET6;
+            cn->ipv6 = ctx->lexer->token.value.ipv6;
+        }
         lexer_get(ctx->lexer);
 
         if (!lexer_force_match(ctx->lexer, LEX_T_RPAREN)) {
@@ -784,8 +791,12 @@ static void
 format_ct_nat(const struct ovnact_ct_nat *cn, const char *name, struct ds *s)
 {
     ds_put_cstr(s, name);
-    if (cn->ip) {
-        ds_put_format(s, "("IP_FMT")", IP_ARGS(cn->ip));
+    if (cn->family == AF_INET) {
+        ds_put_format(s, "("IP_FMT")", IP_ARGS(cn->ipv4));
+    } else if (cn->family == AF_INET6) {
+        ds_put_char(s, '(');
+        ipv6_format_addr(&cn->ipv6, s);
+        ds_put_char(s, ')');
     }
     ds_put_char(s, ';');
 }
@@ -831,9 +842,17 @@ encode_ct_nat(const struct ovnact_ct_nat *cn,
     nat->flags = 0;
     nat->range_af = AF_UNSPEC;
 
-    if (cn->ip) {
+    if (cn->family == AF_INET) {
         nat->range_af = AF_INET;
-        nat->range.addr.ipv4.min = cn->ip;
+        nat->range.addr.ipv4.min = cn->ipv4;
+        if (snat) {
+            nat->flags |= NX_NAT_F_SRC;
+        } else {
+            nat->flags |= NX_NAT_F_DST;
+        }
+    } else if (cn->family == AF_INET6) {
+        nat->range_af = AF_INET6;
+        nat->range.addr.ipv6.min = cn->ipv6;
         if (snat) {
             nat->flags |= NX_NAT_F_SRC;
         } else {
@@ -843,7 +862,7 @@ encode_ct_nat(const struct ovnact_ct_nat *cn,
 
     ofpacts->header = ofpbuf_push_uninit(ofpacts, nat_offset);
     ct = ofpacts->header;
-    if (cn->ip) {
+    if (cn->family == AF_INET || cn->family == AF_INET6) {
         ct->flags |= NX_CT_F_COMMIT;
     }
     ofpact_finish(ofpacts, &ct->ofpact);
diff --git a/tests/ovn.at b/tests/ovn.at
index 9f06059fa..d78689d86 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -1043,15 +1043,18 @@ ct_dnat;
 ct_dnat(192.168.1.2);
     encodes as ct(commit,table=19,zone=NXM_NX_REG11[0..15],nat(dst=192.168.1.2))
     has prereqs ip
+ct_dnat(fd11::2);
+    encodes as ct(commit,table=19,zone=NXM_NX_REG11[0..15],nat(dst=fd11::2))
+    has prereqs ip
 
 ct_dnat(192.168.1.2, 192.168.1.3);
     Syntax error at `,' expecting `)'.
 ct_dnat(foo);
-    Syntax error at `foo' expecting IPv4 address.
+    Syntax error at `foo' expecting IPv4 or IPv6 address.
 ct_dnat(foo, bar);
-    Syntax error at `foo' expecting IPv4 address.
+    Syntax error at `foo' expecting IPv4 or IPv6 address.
 ct_dnat();
-    Syntax error at `)' expecting IPv4 address.
+    Syntax error at `)' expecting IPv4 or IPv6 address.
 
 # ct_snat
 ct_snat;
@@ -1060,15 +1063,18 @@ ct_snat;
 ct_snat(192.168.1.2);
     encodes as ct(commit,table=19,zone=NXM_NX_REG12[0..15],nat(src=192.168.1.2))
     has prereqs ip
+ct_snat(fd11::2);
+    encodes as ct(commit,table=19,zone=NXM_NX_REG12[0..15],nat(src=fd11::2))
+    has prereqs ip
 
 ct_snat(192.168.1.2, 192.168.1.3);
     Syntax error at `,' expecting `)'.
 ct_snat(foo);
-    Syntax error at `foo' expecting IPv4 address.
+    Syntax error at `foo' expecting IPv4 or IPv6 address.
 ct_snat(foo, bar);
-    Syntax error at `foo' expecting IPv4 address.
+    Syntax error at `foo' expecting IPv4 or IPv6 address.
 ct_snat();
-    Syntax error at `)' expecting IPv4 address.
+    Syntax error at `)' expecting IPv4 or IPv6 address.
 
 # ct_clear
 ct_clear;
diff --git a/utilities/ovn-trace.c b/utilities/ovn-trace.c
index c95acb897..ea64dc673 100644
--- a/utilities/ovn-trace.c
+++ b/utilities/ovn-trace.c
@@ -1886,7 +1886,7 @@ execute_ct_nat(const struct ovnact_ct_nat *ct_nat,
                enum ovnact_pipeline pipeline, struct ovs_list *super)
 {
     bool is_dst = ct_nat->ovnact.type == OVNACT_CT_DNAT;
-    if (!is_dst && dp->has_local_l3gateway && !ct_nat->ip) {
+    if (!is_dst && dp->has_local_l3gateway && ct_nat->family == AF_UNSPEC) {
         /* "ct_snat;" has no visible effect in a gateway router. */
         return;
     }
@@ -1897,10 +1897,15 @@ execute_ct_nat(const struct ovnact_ct_nat *ct_nat,
     struct flow ct_flow = *uflow;
     struct ds s = DS_EMPTY_INITIALIZER;
     ds_put_format(&s, "ct_%cnat", direction[0]);
-    if (ct_nat->ip) {
-        ds_put_format(&s, "(ip4.%s="IP_FMT")", direction, IP_ARGS(ct_nat->ip));
-        ovs_be32 *ip = is_dst ? &ct_flow.nw_dst : &ct_flow.nw_src;
-        *ip = ct_nat->ip;
+    if (ct_nat->family != AF_UNSPEC) {
+        if (ct_nat->family == AF_INET) {
+            ds_put_format(&s, "(ip4.%s="IP_FMT")", direction,
+                          IP_ARGS(ct_nat->ipv4));
+        } else {
+            ds_put_format(&s, "(ip6.%s=", direction);
+            ipv6_format_addr(&ct_nat->ipv6, &s);
+            ds_put_char(&s, ')');
+        }
 
         uint8_t state = is_dst ? CS_DST_NAT : CS_SRC_NAT;
         ct_flow.ct_state |= state;
