diff --git a/cluster.go b/cluster.go
index 12417d1f9..d20295c7d 100644
--- a/cluster.go
+++ b/cluster.go
@@ -62,6 +62,7 @@ type ClusterOptions struct {
 
 	OnConnect func(ctx context.Context, cn *Conn) error
 
+	Protocol int
 	Username string
 	Password string
 
@@ -263,6 +264,7 @@ func (opt *ClusterOptions) clientOptions() *Options {
 		Dialer:     opt.Dialer,
 		OnConnect:  opt.OnConnect,
 
+		Protocol: opt.Protocol,
 		Username: opt.Username,
 		Password: opt.Password,
 
diff --git a/cluster_test.go b/cluster_test.go
index be2494241..75ea40df1 100644
--- a/cluster_test.go
+++ b/cluster_test.go
@@ -583,6 +583,35 @@ var _ = Describe("ClusterClient", func() {
 		})
 	}
 
+	Describe("ClusterClient PROTO 2", func() {
+		BeforeEach(func() {
+			opt = redisClusterOptions()
+			opt.Protocol = 2
+			client = cluster.newClusterClient(ctx, opt)
+
+			err := client.ForEachMaster(ctx, func(ctx context.Context, master *redis.Client) error {
+				return master.FlushDB(ctx).Err()
+			})
+			Expect(err).NotTo(HaveOccurred())
+		})
+
+		AfterEach(func() {
+			_ = client.ForEachMaster(ctx, func(ctx context.Context, master *redis.Client) error {
+				return master.FlushDB(ctx).Err()
+			})
+			Expect(client.Close()).NotTo(HaveOccurred())
+		})
+
+		It("should CLUSTER PROTO 2", func() {
+			_ = client.ForEachShard(ctx, func(ctx context.Context, c *redis.Client) error {
+				val, err := c.Do(ctx, "HELLO").Result()
+				Expect(err).NotTo(HaveOccurred())
+				Expect(val).Should(ContainElements("proto", int64(2)))
+				return nil
+			})
+		})
+	})
+
 	Describe("ClusterClient", func() {
 		BeforeEach(func() {
 			opt = redisClusterOptions()
@@ -746,6 +775,15 @@ var _ = Describe("ClusterClient", func() {
 			})
 		})
 
+		It("should CLUSTER PROTO 3", func() {
+			_ = client.ForEachShard(ctx, func(ctx context.Context, c *redis.Client) error {
+				val, err := c.Do(ctx, "HELLO").Result()
+				Expect(err).NotTo(HaveOccurred())
+				Expect(val).Should(HaveKeyWithValue("proto", int64(3)))
+				return nil
+			})
+		})
+
 		It("should CLUSTER MYSHARDID", func() {
 			shardID, err := client.ClusterMyShardID(ctx).Result()
 			Expect(err).NotTo(HaveOccurred())
diff --git a/options.go b/options.go
index a4af5884f..bb4816b27 100644
--- a/options.go
+++ b/options.go
@@ -45,6 +45,9 @@ type Options struct {
 	// Hook that is called when new connection is established.
 	OnConnect func(ctx context.Context, cn *Conn) error
 
+	// Protocol 2 or 3. Use the version to negotiate RESP version with redis-server.
+	// Default is 3.
+	Protocol int
 	// Use the specified Username to authenticate the current connection
 	// with one of the connections defined in the ACL list when connecting
 	// to a Redis 6.0 instance, or greater, that is using the Redis ACL system.
@@ -437,6 +440,7 @@ func setupConnParams(u *url.URL, o *Options) (*Options, error) {
 		o.DB = db
 	}
 
+	o.Protocol = q.int("protocol")
 	o.ClientName = q.string("client_name")
 	o.MaxRetries = q.int("max_retries")
 	o.MinRetryBackoff = q.duration("min_retry_backoff")
diff --git a/options_test.go b/options_test.go
index 4ad917534..fa9ac6c9e 100644
--- a/options_test.go
+++ b/options_test.go
@@ -62,6 +62,9 @@ func TestParseURL(t *testing.T) {
 		}, {
 			url: "redis://localhost:123/?db=2&client_name=hi", // client name
 			o:   &Options{Addr: "localhost:123", DB: 2, ClientName: "hi"},
+		}, {
+			url: "redis://localhost:123/?db=2&protocol=2", // RESP Protocol
+			o:   &Options{Addr: "localhost:123", DB: 2, Protocol: 2},
 		}, {
 			url: "unix:///tmp/redis.sock",
 			o:   &Options{Addr: "/tmp/redis.sock"},
diff --git a/redis.go b/redis.go
index cae12f8c9..c7fbd0de8 100644
--- a/redis.go
+++ b/redis.go
@@ -279,10 +279,15 @@ func (c *baseClient) initConn(ctx context.Context, cn *pool.Conn) error {
 	conn := newConn(c.opt, connPool)
 
 	var auth bool
+	protocol := c.opt.Protocol
+	// By default, use RESP3 in current version.
+	if protocol < 2 {
+		protocol = 3
+	}
 
 	// for redis-server versions that do not support the HELLO command,
 	// RESP2 will continue to be used.
-	if err := conn.Hello(ctx, 3, username, password, "").Err(); err == nil {
+	if err := conn.Hello(ctx, protocol, username, password, "").Err(); err == nil {
 		auth = true
 	} else if !isRedisError(err) {
 		// When the server responds with the RESP protocol and the result is not a normal
diff --git a/redis_test.go b/redis_test.go
index 92b24c729..c9d8df6bf 100644
--- a/redis_test.go
+++ b/redis_test.go
@@ -185,6 +185,33 @@ var _ = Describe("Client", func() {
 		Expect(val).Should(ContainSubstring("name=hi"))
 	})
 
+	It("should client PROTO 2", func() {
+		opt := redisOptions()
+		opt.Protocol = 2
+		db := redis.NewClient(opt)
+
+		defer func() {
+			Expect(db.Close()).NotTo(HaveOccurred())
+		}()
+
+		val, err := db.Do(ctx, "HELLO").Result()
+		Expect(err).NotTo(HaveOccurred())
+		Expect(val).Should(ContainElements("proto", int64(2)))
+	})
+
+	It("should client PROTO 3", func() {
+		opt := redisOptions()
+		db := redis.NewClient(opt)
+
+		defer func() {
+			Expect(db.Close()).NotTo(HaveOccurred())
+		}()
+
+		val, err := db.Do(ctx, "HELLO").Result()
+		Expect(err).NotTo(HaveOccurred())
+		Expect(val).Should(HaveKeyWithValue("proto", int64(3)))
+	})
+
 	It("processes custom commands", func() {
 		cmd := redis.NewCmd(ctx, "PING")
 		_ = client.Process(ctx, cmd)
diff --git a/ring.go b/ring.go
index f924ac0ad..0572ba346 100644
--- a/ring.go
+++ b/ring.go
@@ -12,7 +12,7 @@ import (
 	"time"
 
 	"github.com/cespare/xxhash/v2"
-	rendezvous "github.com/dgryski/go-rendezvous" //nolint
+	"github.com/dgryski/go-rendezvous" //nolint
 
 	"github.com/redis/go-redis/v9/internal"
 	"github.com/redis/go-redis/v9/internal/hashtag"
@@ -70,6 +70,7 @@ type RingOptions struct {
 	Dialer    func(ctx context.Context, network, addr string) (net.Conn, error)
 	OnConnect func(ctx context.Context, cn *Conn) error
 
+	Protocol int
 	Username string
 	Password string
 	DB       int
@@ -136,6 +137,7 @@ func (opt *RingOptions) clientOptions() *Options {
 		Dialer:     opt.Dialer,
 		OnConnect:  opt.OnConnect,
 
+		Protocol: opt.Protocol,
 		Username: opt.Username,
 		Password: opt.Password,
 		DB:       opt.DB,
diff --git a/ring_test.go b/ring_test.go
index 73bb2fc49..b3017f616 100644
--- a/ring_test.go
+++ b/ring_test.go
@@ -15,6 +15,37 @@ import (
 	"github.com/redis/go-redis/v9"
 )
 
+var _ = Describe("Redis Ring PROTO 2", func() {
+	const heartbeat = 100 * time.Millisecond
+
+	var ring *redis.Ring
+
+	BeforeEach(func() {
+		opt := redisRingOptions()
+		opt.Protocol = 2
+		opt.HeartbeatFrequency = heartbeat
+		ring = redis.NewRing(opt)
+
+		err := ring.ForEachShard(ctx, func(ctx context.Context, cl *redis.Client) error {
+			return cl.FlushDB(ctx).Err()
+		})
+		Expect(err).NotTo(HaveOccurred())
+	})
+
+	AfterEach(func() {
+		Expect(ring.Close()).NotTo(HaveOccurred())
+	})
+
+	It("should ring PROTO 2", func() {
+		_ = ring.ForEachShard(ctx, func(ctx context.Context, c *redis.Client) error {
+			val, err := c.Do(ctx, "HELLO").Result()
+			Expect(err).NotTo(HaveOccurred())
+			Expect(val).Should(ContainElements("proto", int64(2)))
+			return nil
+		})
+	})
+})
+
 var _ = Describe("Redis Ring", func() {
 	const heartbeat = 100 * time.Millisecond
 
@@ -65,6 +96,15 @@ var _ = Describe("Redis Ring", func() {
 		})
 	})
 
+	It("should ring PROTO 3", func() {
+		_ = ring.ForEachShard(ctx, func(ctx context.Context, c *redis.Client) error {
+			val, err := c.Do(ctx, "HELLO").Result()
+			Expect(err).NotTo(HaveOccurred())
+			Expect(val).Should(HaveKeyWithValue("proto", int64(3)))
+			return nil
+		})
+	})
+
 	It("distributes keys", func() {
 		setRingKeys()
 
diff --git a/sentinel.go b/sentinel.go
index 5ea41f17b..dbff40603 100644
--- a/sentinel.go
+++ b/sentinel.go
@@ -54,6 +54,7 @@ type FailoverOptions struct {
 	Dialer    func(ctx context.Context, network, addr string) (net.Conn, error)
 	OnConnect func(ctx context.Context, cn *Conn) error
 
+	Protocol int
 	Username string
 	Password string
 	DB       int
@@ -88,6 +89,7 @@ func (opt *FailoverOptions) clientOptions() *Options {
 		OnConnect: opt.OnConnect,
 
 		DB:       opt.DB,
+		Protocol: opt.Protocol,
 		Username: opt.Username,
 		Password: opt.Password,
 
@@ -151,6 +153,7 @@ func (opt *FailoverOptions) clusterOptions() *ClusterOptions {
 		Dialer:    opt.Dialer,
 		OnConnect: opt.OnConnect,
 
+		Protocol: opt.Protocol,
 		Username: opt.Username,
 		Password: opt.Password,
 
diff --git a/sentinel_test.go b/sentinel_test.go
index fc9dbcbc5..705b6a094 100644
--- a/sentinel_test.go
+++ b/sentinel_test.go
@@ -10,6 +10,30 @@ import (
 	"github.com/redis/go-redis/v9"
 )
 
+var _ = Describe("Sentinel PROTO 2", func() {
+	var client *redis.Client
+
+	BeforeEach(func() {
+		client = redis.NewFailoverClient(&redis.FailoverOptions{
+			MasterName:    sentinelName,
+			SentinelAddrs: sentinelAddrs,
+			MaxRetries:    -1,
+			Protocol:      2,
+		})
+		Expect(client.FlushDB(ctx).Err()).NotTo(HaveOccurred())
+	})
+
+	AfterEach(func() {
+		_ = client.Close()
+	})
+
+	It("should sentinel client PROTO 2", func() {
+		val, err := client.Do(ctx, "HELLO").Result()
+		Expect(err).NotTo(HaveOccurred())
+		Expect(val).Should(ContainElements("proto", int64(2)))
+	})
+})
+
 var _ = Describe("Sentinel", func() {
 	var client *redis.Client
 	var master *redis.Client
@@ -134,6 +158,40 @@ var _ = Describe("Sentinel", func() {
 		Expect(err).NotTo(HaveOccurred())
 		Expect(val).Should(ContainSubstring("name=sentinel_hi"))
 	})
+
+	It("should sentinel client PROTO 3", func() {
+		val, err := client.Do(ctx, "HELLO").Result()
+		Expect(err).NotTo(HaveOccurred())
+		Expect(val).Should(HaveKeyWithValue("proto", int64(3)))
+	})
+})
+
+var _ = Describe("NewFailoverClusterClient PROTO 2", func() {
+	var client *redis.ClusterClient
+
+	BeforeEach(func() {
+		client = redis.NewFailoverClusterClient(&redis.FailoverOptions{
+			MasterName:    sentinelName,
+			SentinelAddrs: sentinelAddrs,
+			Protocol:      2,
+
+			RouteRandomly: true,
+		})
+		Expect(client.FlushDB(ctx).Err()).NotTo(HaveOccurred())
+	})
+
+	AfterEach(func() {
+		_ = client.Close()
+	})
+
+	It("should sentinel cluster PROTO 2", func() {
+		_ = client.ForEachShard(ctx, func(ctx context.Context, c *redis.Client) error {
+			val, err := client.Do(ctx, "HELLO").Result()
+			Expect(err).NotTo(HaveOccurred())
+			Expect(val).Should(ContainElements("proto", int64(2)))
+			return nil
+		})
+	})
 })
 
 var _ = Describe("NewFailoverClusterClient", func() {
@@ -237,6 +295,15 @@ var _ = Describe("NewFailoverClusterClient", func() {
 			return nil
 		})
 	})
+
+	It("should sentinel cluster PROTO 3", func() {
+		_ = client.ForEachShard(ctx, func(ctx context.Context, c *redis.Client) error {
+			val, err := client.Do(ctx, "HELLO").Result()
+			Expect(err).NotTo(HaveOccurred())
+			Expect(val).Should(HaveKeyWithValue("proto", int64(3)))
+			return nil
+		})
+	})
 })
 
 var _ = Describe("SentinelAclAuth", func() {
diff --git a/universal.go b/universal.go
index 9d1a8520a..53ece1856 100644
--- a/universal.go
+++ b/universal.go
@@ -26,6 +26,7 @@ type UniversalOptions struct {
 	Dialer    func(ctx context.Context, network, addr string) (net.Conn, error)
 	OnConnect func(ctx context.Context, cn *Conn) error
 
+	Protocol         int
 	Username         string
 	Password         string
 	SentinelUsername string
@@ -77,6 +78,7 @@ func (o *UniversalOptions) Cluster() *ClusterOptions {
 		Dialer:     o.Dialer,
 		OnConnect:  o.OnConnect,
 
+		Protocol: o.Protocol,
 		Username: o.Username,
 		Password: o.Password,
 
@@ -122,6 +124,7 @@ func (o *UniversalOptions) Failover() *FailoverOptions {
 		OnConnect: o.OnConnect,
 
 		DB:               o.DB,
+		Protocol:         o.Protocol,
 		Username:         o.Username,
 		Password:         o.Password,
 		SentinelUsername: o.SentinelUsername,
@@ -162,6 +165,7 @@ func (o *UniversalOptions) Simple() *Options {
 		OnConnect:  o.OnConnect,
 
 		DB:       o.DB,
+		Protocol: o.Protocol,
 		Username: o.Username,
 		Password: o.Password,