diff --git a/app/dns/nameserver_quic.go b/app/dns/nameserver_quic.go
index 435cd0a35347..0896b62d9664 100644
--- a/app/dns/nameserver_quic.go
+++ b/app/dns/nameserver_quic.go
@@ -9,7 +9,7 @@ import (
 	"sync/atomic"
 	"time"
 
-	"github.com/quic-go/quic-go"
+	"github.com/refraction-networking/uquic"
 	"github.com/xtls/xray-core/common"
 	"github.com/xtls/xray-core/common/buf"
 	"github.com/xtls/xray-core/common/errors"
@@ -400,7 +400,9 @@ func (s *QUICNameServer) openConnection() (quic.Connection, error) {
 		HandshakeIdleTimeout: handshakeTimeout,
 	}
 	tlsConfig.ServerName = s.destination.Address.String()
-	conn, err := quic.DialAddr(context.Background(), s.destination.NetAddr(), tlsConfig.GetTLSConfig(tls.WithNextProto("http/1.1", http2.NextProtoTLS, NextProtoDQ)), quicConfig)
+	utlsConf := tls.CopyConfig(tlsConfig.GetTLSConfig())
+	utlsConf.NextProtos = []string{ "http/1.1", http2.NextProtoTLS, NextProtoDQ }
+	conn, err := quic.DialAddr(context.Background(), s.destination.NetAddr(), utlsConf, quicConfig)
 	log.Record(&log.AccessMessage{
 		From:   "DNS",
 		To:     s.destination,
diff --git a/common/protocol/quic/sniff.go b/common/protocol/quic/sniff.go
index 8719a085ddef..b5a7517d730a 100644
--- a/common/protocol/quic/sniff.go
+++ b/common/protocol/quic/sniff.go
@@ -7,7 +7,7 @@ import (
 	"encoding/binary"
 	"io"
 
-	"github.com/quic-go/quic-go/quicvarint"
+	"github.com/refraction-networking/uquic/quicvarint"
 	"github.com/xtls/xray-core/common"
 	"github.com/xtls/xray-core/common/buf"
 	"github.com/xtls/xray-core/common/bytespool"
diff --git a/go.mod b/go.mod
index 7f3cba9f4be6..27ffdd783cd1 100644
--- a/go.mod
+++ b/go.mod
@@ -12,7 +12,7 @@ require (
 	github.com/miekg/dns v1.1.62
 	github.com/pelletier/go-toml v1.9.5
 	github.com/pires/go-proxyproto v0.8.0
-	github.com/quic-go/quic-go v0.46.0
+	github.com/refraction-networking/uquic v0.0.6
 	github.com/refraction-networking/utls v1.6.7
 	github.com/sagernet/sing v0.4.3
 	github.com/sagernet/sing-shadowsocks v0.2.7
@@ -38,11 +38,15 @@ require (
 	github.com/andybalholm/brotli v1.1.0 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
 	github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // indirect
+	github.com/gaukas/clienthellod v0.4.2 // indirect
+	github.com/gaukas/godicttls v0.0.4 // indirect
 	github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
 	github.com/google/btree v1.1.2 // indirect
+	github.com/google/gopacket v1.1.19 // indirect
 	github.com/google/pprof v0.0.0-20240528025155-186aa0362fba // indirect
 	github.com/klauspost/compress v1.17.8 // indirect
 	github.com/klauspost/cpuid/v2 v2.2.7 // indirect
+	github.com/kr/pretty v0.3.1 // indirect
 	github.com/onsi/ginkgo/v2 v2.19.0 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/quic-go/qpack v0.4.0 // indirect
diff --git a/go.sum b/go.sum
index 8fa32abd6248..4952bcd2ddfd 100644
--- a/go.sum
+++ b/go.sum
@@ -4,12 +4,17 @@ github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1
 github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
 github.com/cloudflare/circl v1.4.0 h1:BV7h5MgrktNzytKmWjpOtdYrf0lkkbF8YMlBGPhJQrY=
 github.com/cloudflare/circl v1.4.0/go.mod h1:PDRU+oXvdD7KCtgKxW95M5Z8BpSCJXQORiZFnBQS5QU=
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/dgryski/go-metro v0.0.0-20200812162917-85c65e2d0165/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
 github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 h1:y7y0Oa6UawqTFPCDw9JG6pdKt4F9pAhHv0B7FMGaGD0=
 github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
+github.com/gaukas/clienthellod v0.4.2 h1:LPJ+LSeqt99pqeCV4C0cllk+pyWmERisP7w6qWr7eqE=
+github.com/gaukas/clienthellod v0.4.2/go.mod h1:M57+dsu0ZScvmdnNxaxsDPM46WhSEdPYAOdNgfL7IKA=
+github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk=
+github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI=
 github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4=
 github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I=
 github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
@@ -22,6 +27,8 @@ github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
 github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
 github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
 github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
+github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
 github.com/google/pprof v0.0.0-20240528025155-186aa0362fba h1:ql1qNgCyOB7iAEk8JTNM+zJrgIbnyCKX/wdlyPufP5g=
 github.com/google/pprof v0.0.0-20240528025155-186aa0362fba/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
 github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
@@ -32,6 +39,10 @@ github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0N
 github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
 github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
 github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
+github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
+github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=
 github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ=
 github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA=
@@ -44,16 +55,19 @@ github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoU
 github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE=
 github.com/pires/go-proxyproto v0.8.0 h1:5unRmEAPbHXHuLjDg01CxJWf91cw3lKHc/0xzKpXEe0=
 github.com/pires/go-proxyproto v0.8.0/go.mod h1:iknsfgnH8EkjrMeMyvfKByp9TiBZCKZM0jx2xmKqnVY=
+github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
 github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
-github.com/quic-go/quic-go v0.46.0 h1:uuwLClEEyk1DNvchH8uCByQVjo3yKL9opKulExNDs7Y=
-github.com/quic-go/quic-go v0.46.0/go.mod h1:1dLehS7TIR64+vxGR70GDcatWTOtMX2PUtnKsjbTurI=
+github.com/refraction-networking/uquic v0.0.6 h1:9ol1oOaOpHDeeDlBY7u228jK+T5oic35QrFimHVaCMM=
+github.com/refraction-networking/uquic v0.0.6/go.mod h1:TFgTmV/yqVCMEXVwP7z7PMAhzye02rFHLV6cRAg59jc=
 github.com/refraction-networking/utls v1.6.7 h1:zVJ7sP1dJx/WtVuITug3qYUq034cDq9B2MR1K67ULZM=
 github.com/refraction-networking/utls v1.6.7/go.mod h1:BC3O4vQzye5hqpmDTWUqi4P5DDhzJfkV1tdqtawQIH0=
 github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
 github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
+github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
+github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
 github.com/sagernet/sing v0.4.3 h1:Ty/NAiNnVd6844k7ujlL5lkzydhcTH5Psc432jXA4Y8=
 github.com/sagernet/sing v0.4.3/go.mod h1:ieZHA/+Y9YZfXs2I3WtuwgyCZ6GPsIR7HdKb1SdEnls=
 github.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8=
@@ -83,6 +97,8 @@ golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
 golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
 golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc h1:O9NuF4s+E/PvMIy+9IUZB9znFwUIXEWSstNjek6VpVg=
 golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
+golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
 golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
 golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
 golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
@@ -115,6 +131,7 @@ golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
 golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
 golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
 golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
@@ -131,8 +148,9 @@ google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E=
 google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA=
 google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
 google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
diff --git a/transport/internet/http/dialer.go b/transport/internet/http/dialer.go
index d0a86e8b4eb2..64801e6807a6 100644
--- a/transport/internet/http/dialer.go
+++ b/transport/internet/http/dialer.go
@@ -9,8 +9,9 @@ import (
 	"sync"
 	"time"
 
-	"github.com/quic-go/quic-go"
-	"github.com/quic-go/quic-go/http3"
+	"github.com/refraction-networking/uquic"
+	"github.com/refraction-networking/uquic/http3"
+	utls "github.com/refraction-networking/utls"
 	"github.com/xtls/xray-core/common"
 	"github.com/xtls/xray-core/common/buf"
 	c "github.com/xtls/xray-core/common/ctx"
@@ -79,9 +80,9 @@ func getHTTPClient(ctx context.Context, dest net.Destination, streamSettings *in
 			KeepAlivePeriod:    h3KeepalivePeriod,
 		}
 		roundTripper := &http3.RoundTripper{
-			QUICConfig:      quicConfig,
-			TLSClientConfig: tlsConfigs.GetTLSConfig(tls.WithDestination(dest)),
-			Dial: func(ctx context.Context, addr string, tlsCfg *gotls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
+			QuicConfig:      quicConfig,
+			TLSClientConfig: tls.CopyConfig(tlsConfigs.GetTLSConfig(tls.WithDestination(dest))),
+			Dial: func(ctx context.Context, addr string, tlsCfg *utls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
 				conn, err := internet.DialSystem(ctx, dest, streamSettings.SocketSettings)
 				if err != nil {
 					return nil, err
@@ -119,6 +120,15 @@ func getHTTPClient(ctx context.Context, dest net.Destination, streamSettings *in
 			},
 		}
 		transport = roundTripper
+
+		if fingerprint := tls.GetQuicFingerprint(tlsConfigs.Fingerprint); fingerprint != nil {
+			quicSpec, err := quic.QUICID2Spec(*fingerprint)
+			if err != nil {
+				errors.LogError(ctx, "unknown fingerprint: ", tlsConfigs.Fingerprint)
+			} else {
+				transport = http3.GetURoundTripper(roundTripper, &quicSpec, nil)
+			}
+		}
 	} else {
 		transportH2 := &http2.Transport{
 			DialTLSContext: func(hctx context.Context, string, addr string, tlsConfig *gotls.Config) (net.Conn, error) {
diff --git a/transport/internet/http/hub.go b/transport/internet/http/hub.go
index 96fe8f629d43..7368985b39d0 100644
--- a/transport/internet/http/hub.go
+++ b/transport/internet/http/hub.go
@@ -8,8 +8,8 @@ import (
 	"strings"
 	"time"
 
-	"github.com/quic-go/quic-go"
-	"github.com/quic-go/quic-go/http3"
+	"github.com/refraction-networking/uquic"
+	"github.com/refraction-networking/uquic/http3"
 	goreality "github.com/xtls/reality"
 	"github.com/xtls/xray-core/common"
 	"github.com/xtls/xray-core/common/errors"
@@ -170,7 +170,7 @@ func Listen(ctx context.Context, address net.Address, port net.Port, streamSetti
 		if err != nil {
 			return nil,  errors.New("failed to listen UDP(for SH3) on ", address, ":", port).Base(err)
 		}
-		h3listener, err := quic.ListenEarly(Conn, tlsConfig, nil)
+		h3listener, err := quic.ListenEarly(Conn, config.GetUTLSConfig(), nil)
 		if err != nil {
 			return nil, errors.New("failed to listen QUIC(for SH3) on ", address, ":", port).Base(err)
 		}
diff --git a/transport/internet/splithttp/dialer.go b/transport/internet/splithttp/dialer.go
index c43783ec0dc4..a3f399377c45 100644
--- a/transport/internet/splithttp/dialer.go
+++ b/transport/internet/splithttp/dialer.go
@@ -9,8 +9,9 @@ import (
 	"sync"
 	"time"
 
-	"github.com/quic-go/quic-go"
-	"github.com/quic-go/quic-go/http3"
+	"github.com/refraction-networking/uquic"
+	"github.com/refraction-networking/uquic/http3"
+	utls "github.com/refraction-networking/utls"
 	"github.com/xtls/xray-core/common"
 	"github.com/xtls/xray-core/common/buf"
 	"github.com/xtls/xray-core/common/errors"
@@ -72,7 +73,7 @@ func getHTTPClient(ctx context.Context, dest net.Destination, streamSettings *in
 		}
 
 		muxManager = NewMuxManager(mux, func() interface{} {
-			return createHTTPClient(dest, streamSettings)
+			return createHTTPClient(ctx, dest, streamSettings)
 		})
 		globalDialerMap[key] = muxManager
 	}
@@ -81,7 +82,7 @@ func getHTTPClient(ctx context.Context, dest net.Destination, streamSettings *in
 	return res.Resource.(DialerClient), res
 }
 
-func createHTTPClient(dest net.Destination, streamSettings *internet.MemoryStreamConfig) DialerClient {
+func createHTTPClient(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) DialerClient {
 	tlsConfig := tls.ConfigFromStreamSettings(streamSettings)
 	realityConfig := reality.ConfigFromStreamSettings(streamSettings)
 
@@ -144,10 +145,10 @@ func createHTTPClient(dest net.Destination, streamSettings *internet.MemoryStrea
 			MaxIncomingStreams: -1,
 			KeepAlivePeriod:    h3KeepalivePeriod,
 		}
-		transport = &http3.RoundTripper{
-			QUICConfig:      quicConfig,
-			TLSClientConfig: gotlsConfig,
-			Dial: func(ctx context.Context, addr string, tlsCfg *gotls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
+		roundTripper := &http3.RoundTripper{
+			QuicConfig:      quicConfig,
+			TLSClientConfig: tls.CopyConfig(gotlsConfig),
+			Dial: func(ctx context.Context, addr string, tlsCfg *utls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
 				conn, err := internet.DialSystem(ctx, dest, streamSettings.SocketSettings)
 				if err != nil {
 					return nil, err
@@ -184,6 +185,15 @@ func createHTTPClient(dest net.Destination, streamSettings *internet.MemoryStrea
 				return quic.DialEarly(ctx, udpConn, udpAddr, tlsCfg, cfg)
 			},
 		}
+		transport = roundTripper
+		if fingerprint := tls.GetQuicFingerprint(tlsConfig.Fingerprint); fingerprint != nil {
+			quicSpec, err := quic.QUICID2Spec(*fingerprint)
+			if err != nil {
+				errors.LogError(ctx, "unknown fingerprint: ", tlsConfig.Fingerprint)
+			} else {
+				transport = http3.GetURoundTripper(roundTripper, &quicSpec, nil)
+			}
+		}
 	} else if isH2 {
 		transport = &http2.Transport{
 			DialTLSContext: func(ctxInner context.Context, network string, addr string, cfg *gotls.Config) (net.Conn, error) {
diff --git a/transport/internet/splithttp/hub.go b/transport/internet/splithttp/hub.go
index b7244f10a619..39c191d20802 100644
--- a/transport/internet/splithttp/hub.go
+++ b/transport/internet/splithttp/hub.go
@@ -11,8 +11,8 @@ import (
 	"sync"
 	"time"
 
-	"github.com/quic-go/quic-go"
-	"github.com/quic-go/quic-go/http3"
+	"github.com/refraction-networking/uquic"
+	"github.com/refraction-networking/uquic/http3"
 	goreality "github.com/xtls/reality"
 	"github.com/xtls/xray-core/common"
 	"github.com/xtls/xray-core/common/errors"
@@ -308,7 +308,8 @@ func ListenSH(ctx context.Context, address net.Address, port net.Port, streamSet
 		if err != nil {
 			return nil, errors.New("failed to listen UDP(for SH3) on ", address, ":", port).Base(err)
 		}
-		h3listener, err := quic.ListenEarly(Conn, tlsConfig, nil)
+		config := v2tls.ConfigFromStreamSettings(streamSettings)
+		h3listener, err := quic.ListenEarly(Conn, config.GetUTLSConfig(), nil)
 		if err != nil {
 			return nil, errors.New("failed to listen QUIC(for SH3) on ", address, ":", port).Base(err)
 		}
diff --git a/transport/internet/tls/config.go b/transport/internet/tls/config.go
index b6c55c00bfb4..ba517a13d5f9 100644
--- a/transport/internet/tls/config.go
+++ b/transport/internet/tls/config.go
@@ -1,6 +1,7 @@
 package tls
 
 import (
+	"bytes"
 	"context"
 	"crypto/hmac"
 	"crypto/tls"
@@ -10,8 +11,8 @@ import (
 	"strings"
 	"sync"
 	"time"
-	"bytes"
 
+	utls "github.com/refraction-networking/utls"
 	"github.com/xtls/xray-core/common/errors"
 	"github.com/xtls/xray-core/common/net"
 	"github.com/xtls/xray-core/common/ocsp"
@@ -21,6 +22,7 @@ import (
 )
 
 var globalSessionCache = tls.NewLRUClientSessionCache(128)
+var globalSessionCacheU = utls.NewLRUClientSessionCache(128)
 
 // ParseCertificate converts a cert.Certificate to Certificate.
 func ParseCertificate(c *cert.Certificate) *Certificate {
@@ -92,6 +94,54 @@ func (c *Config) BuildCertificates() []*tls.Certificate {
 	return certs
 }
 
+// BuildCertificates builds a list of UTLS certificates from proto definition.
+func (c *Config) BuildCertificatesU() []*utls.Certificate {
+	certs := make([]*utls.Certificate, 0, len(c.Certificate))
+	for _, entry := range c.Certificate {
+		if entry.Usage != Certificate_ENCIPHERMENT {
+			continue
+		}
+		getX509KeyPair := func() *utls.Certificate {
+			keyPair, err := utls.X509KeyPair(entry.Certificate, entry.Key)
+			if err != nil {
+				errors.LogWarningInner(context.Background(), err, "ignoring invalid X509 key pair")
+				return nil
+			}
+			keyPair.Leaf, err = x509.ParseCertificate(keyPair.Certificate[0])
+			if err != nil {
+				errors.LogWarningInner(context.Background(), err, "ignoring invalid certificate")
+				return nil
+			}
+			return &keyPair
+		}
+		if keyPair := getX509KeyPair(); keyPair != nil {
+			certs = append(certs, keyPair)
+		} else {
+			continue
+		}
+		index := len(certs) - 1
+		setupOcspTicker(entry, func(isReloaded, isOcspstapling bool){
+			cert := certs[index]
+			if isReloaded {
+				if newKeyPair := getX509KeyPair(); newKeyPair != nil {
+					cert = newKeyPair
+				} else {
+					return
+				}
+			}
+			if isOcspstapling {
+				if newOCSPData, err := ocsp.GetOCSPForCert(cert.Certificate); err != nil {
+					errors.LogWarningInner(context.Background(), err, "ignoring invalid OCSP")
+				} else if string(newOCSPData) != string(cert.OCSPStaple) {
+					cert.OCSPStaple = newOCSPData
+				}
+			}
+			certs[index] = cert
+		})
+	}
+	return certs
+}
+
 func setupOcspTicker(entry *Certificate, callback func(isReloaded, isOcspstapling bool)) {
 	go func() {
 		if entry.OneTimeLoading {
@@ -140,6 +190,17 @@ func isCertificateExpired(c *tls.Certificate) bool {
 	return c.Leaf != nil && c.Leaf.NotAfter.Before(time.Now().Add(time.Minute*2))
 }
 
+func isCertificateExpiredU(c *utls.Certificate) bool {
+	if c.Leaf == nil && len(c.Certificate) > 0 {
+		if pc, err := x509.ParseCertificate(c.Certificate[0]); err == nil {
+			c.Leaf = pc
+		}
+	}
+
+	// If leaf is not there, the certificate is probably not used yet. We trust user to provide a valid certificate.
+	return c.Leaf != nil && c.Leaf.NotAfter.Before(time.Now().Add(time.Minute*2))
+}
+
 func issueCertificate(rawCA *Certificate, domain string) (*tls.Certificate, error) {
 	parent, err := cert.ParseCertificate(rawCA.Certificate, rawCA.Key)
 	if err != nil {
@@ -157,6 +218,23 @@ func issueCertificate(rawCA *Certificate, domain string) (*tls.Certificate, erro
 	return &cert, err
 }
 
+func issueCertificateU(rawCA *Certificate, domain string) (*utls.Certificate, error) {
+	parent, err := cert.ParseCertificate(rawCA.Certificate, rawCA.Key)
+	if err != nil {
+		return nil, errors.New("failed to parse raw certificate").Base(err)
+	}
+	newCert, err := cert.Generate(parent, cert.CommonName(domain), cert.DNSNames(domain))
+	if err != nil {
+		return nil, errors.New("failed to generate new certificate for ", domain).Base(err)
+	}
+	newCertPEM, newKeyPEM := newCert.ToPEM()
+	if rawCA.BuildChain {
+		newCertPEM = bytes.Join([][]byte{newCertPEM, rawCA.Certificate}, []byte("\n"))
+	}
+	cert, err := utls.X509KeyPair(newCertPEM, newKeyPEM)
+	return &cert, err
+}
+
 func (c *Config) getCustomCA() []*Certificate {
 	certs := make([]*Certificate, 0, len(c.Certificate))
 	for _, certificate := range c.Certificate {
@@ -272,6 +350,110 @@ func getNewGetCertificateFunc(certs []*tls.Certificate, rejectUnknownSNI bool) f
 	}
 }
 
+func getGetCertificateUFunc(c *utls.Config, ca []*Certificate) func(hello *utls.ClientHelloInfo) (*utls.Certificate, error) {
+	var access sync.RWMutex
+
+	return func(hello *utls.ClientHelloInfo) (*utls.Certificate, error) {
+		domain := hello.ServerName
+		certExpired := false
+
+		access.RLock()
+		certificate, found := c.NameToCertificate[domain]
+		access.RUnlock()
+
+		if found {
+			if !isCertificateExpiredU(certificate) {
+				return certificate, nil
+			}
+			certExpired = true
+		}
+
+		if certExpired {
+			newCerts := make([]utls.Certificate, 0, len(c.Certificates))
+
+			access.Lock()
+			for _, certificate := range c.Certificates {
+				if !isCertificateExpiredU(&certificate) {
+					newCerts = append(newCerts, certificate)
+				} else if certificate.Leaf != nil {
+					expTime := certificate.Leaf.NotAfter.Format(time.RFC3339)
+					errors.LogInfo(context.Background(), "old certificate for ", domain, " (expire on ", expTime, ") discarded")
+				}
+			}
+
+			c.Certificates = newCerts
+			access.Unlock()
+		}
+
+		var issuedCertificate *utls.Certificate
+
+		// Create a new certificate from existing CA if possible
+		for _, rawCert := range ca {
+			if rawCert.Usage == Certificate_AUTHORITY_ISSUE {
+				newCert, err := issueCertificateU(rawCert, domain)
+				if err != nil {
+					errors.LogInfoInner(context.Background(), err, "failed to issue new certificate for ", domain)
+					continue
+				}
+				parsed, err := x509.ParseCertificate(newCert.Certificate[0])
+				if err == nil {
+					newCert.Leaf = parsed
+					expTime := parsed.NotAfter.Format(time.RFC3339)
+					errors.LogInfo(context.Background(), "new certificate for ", domain, " (expire on ", expTime, ") issued")
+				} else {
+					errors.LogInfoInner(context.Background(), err, "failed to parse new certificate for ", domain)
+				}
+
+				access.Lock()
+				c.Certificates = append(c.Certificates, *newCert)
+				issuedCertificate = &c.Certificates[len(c.Certificates)-1]
+				access.Unlock()
+				break
+			}
+		}
+
+		if issuedCertificate == nil {
+			return nil, errors.New("failed to create a new certificate for ", domain)
+		}
+
+		access.Lock()
+		c.BuildNameToCertificate()
+		access.Unlock()
+
+		return issuedCertificate, nil
+	}
+}
+
+func getNewGetCertificateUFunc(certs []*utls.Certificate, rejectUnknownSNI bool) func(hello *utls.ClientHelloInfo) (*utls.Certificate, error) {
+	return func(hello *utls.ClientHelloInfo) (*utls.Certificate, error) {
+		if len(certs) == 0 {
+			return nil, errNoCertificates
+		}
+		sni := strings.ToLower(hello.ServerName)
+		if !rejectUnknownSNI && (len(certs) == 1 || sni == "") {
+			return certs[0], nil
+		}
+		gsni := "*"
+		if index := strings.IndexByte(sni, '.'); index != -1 {
+			gsni += sni[index:]
+		}
+		for _, keyPair := range certs {
+			if keyPair.Leaf.Subject.CommonName == sni || keyPair.Leaf.Subject.CommonName == gsni {
+				return keyPair, nil
+			}
+			for _, name := range keyPair.Leaf.DNSNames {
+				if name == sni || name == gsni {
+					return keyPair, nil
+				}
+			}
+		}
+		if rejectUnknownSNI {
+			return nil, errNoCertificates
+		}
+		return certs[0], nil
+	}
+}
+
 func (c *Config) parseServerName() string {
 	return c.ServerName
 }
@@ -394,6 +576,97 @@ func (c *Config) GetTLSConfig(opts ...Option) *tls.Config {
 	return config
 }
 
+// GetUTLSConfig converts this Config into utls.Config.
+func (c *Config) GetUTLSConfig() *utls.Config {
+	root, err := c.getCertPool()
+	if err != nil {
+		errors.LogErrorInner(context.Background(), err, "failed to load system root certificate")
+	}
+
+	if c == nil {
+		return &utls.Config{
+			ClientSessionCache:     globalSessionCacheU,
+			RootCAs:                root,
+			InsecureSkipVerify:     false,
+			NextProtos:             nil,
+			SessionTicketsDisabled: true,
+		}
+	}
+
+	config := &utls.Config{
+		ClientSessionCache:     globalSessionCacheU,
+		RootCAs:                root,
+		InsecureSkipVerify:     c.AllowInsecure,
+		NextProtos:             c.NextProtocol,
+		SessionTicketsDisabled: !c.EnableSessionResumption,
+		VerifyPeerCertificate:  c.verifyPeerCert,
+	}
+
+	// for _, opt := range opts {
+	// 	opt(config)
+	// }
+
+	caCerts := c.getCustomCA()
+	if len(caCerts) > 0 {
+		config.GetCertificate = getGetCertificateUFunc(config, caCerts)
+	} else {
+		config.GetCertificate = getNewGetCertificateUFunc(c.BuildCertificatesU(), c.RejectUnknownSni)
+	}
+
+	if sn := c.parseServerName(); len(sn) > 0 {
+		config.ServerName = sn
+	}
+
+	if len(config.NextProtos) == 0 {
+		config.NextProtos = []string{"h2", "http/1.1"}
+	}
+
+	switch c.MinVersion {
+	case "1.0":
+		config.MinVersion = tls.VersionTLS10
+	case "1.1":
+		config.MinVersion = tls.VersionTLS11
+	case "1.2":
+		config.MinVersion = tls.VersionTLS12
+	case "1.3":
+		config.MinVersion = tls.VersionTLS13
+	}
+
+	switch c.MaxVersion {
+	case "1.0":
+		config.MaxVersion = tls.VersionTLS10
+	case "1.1":
+		config.MaxVersion = tls.VersionTLS11
+	case "1.2":
+		config.MaxVersion = tls.VersionTLS12
+	case "1.3":
+		config.MaxVersion = tls.VersionTLS13
+	}
+
+	if len(c.CipherSuites) > 0 {
+		id := make(map[string]uint16)
+		for _, s := range tls.CipherSuites() {
+			id[s.Name] = s.ID
+		}
+		for _, n := range strings.Split(c.CipherSuites, ":") {
+			if id[n] != 0 {
+				config.CipherSuites = append(config.CipherSuites, id[n])
+			}
+		}
+	}
+
+	if len(c.MasterKeyLog) > 0 && c.MasterKeyLog != "none" {
+		writer, err := os.OpenFile(c.MasterKeyLog, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0644)
+		if err != nil {
+			errors.LogErrorInner(context.Background(), err, "failed to open ", c.MasterKeyLog, " as master key log")
+		} else {
+			config.KeyLogWriter = writer
+		}
+	}
+
+	return config
+}
+
 // Option for building TLS config.
 type Option func(*tls.Config)
 
diff --git a/transport/internet/tls/tls.go b/transport/internet/tls/tls.go
index 38b603c02b21..740bf9d43238 100644
--- a/transport/internet/tls/tls.go
+++ b/transport/internet/tls/tls.go
@@ -7,6 +7,7 @@ import (
 	"math/big"
 	"time"
 
+	"github.com/refraction-networking/uquic"
 	utls "github.com/refraction-networking/utls"
 	"github.com/xtls/xray-core/common/buf"
 	"github.com/xtls/xray-core/common/net"
@@ -129,11 +130,11 @@ func (c *UConn) NegotiatedProtocol() string {
 }
 
 func UClient(c net.Conn, config *tls.Config, fingerprint *utls.ClientHelloID) net.Conn {
-	utlsConn := utls.UClient(c, copyConfig(config), *fingerprint)
+	utlsConn := utls.UClient(c, CopyConfig(config), *fingerprint)
 	return &UConn{UConn: utlsConn}
 }
 
-func copyConfig(c *tls.Config) *utls.Config {
+func CopyConfig(c *tls.Config) *utls.Config {
 	return &utls.Config{
 		RootCAs:               c.RootCAs,
 		ServerName:            c.ServerName,
@@ -154,6 +155,18 @@ func init() {
 		}
 		i++
 	}
+
+	bigInt, _ = rand.Int(rand.Reader, big.NewInt(int64(len(QuicAllFingerprints))))
+	stopAt = int(bigInt.Int64())
+	i = 0
+	for _, v := range QuicAllFingerprints {
+		if i == stopAt {
+			QuicPresetFingerprints["random"] = v
+			break
+		}
+		i++
+	}
+
 	weights := utls.DefaultWeights
 	weights.TLSVersMax_Set_VersionTLS13 = 1
 	weights.FirstKeyShare_Set_CurveP256 = 0
@@ -179,6 +192,35 @@ func GetFingerprint(name string) (fingerprint *utls.ClientHelloID) {
 	return
 }
 
+func GetQuicFingerprint(name string) (fingerprint *quic.QUICID) {
+	if name == "" {
+		return
+	}
+	if fingerprint = QuicPresetFingerprints[name]; fingerprint != nil {
+		return
+	}
+	if fingerprint = QuicAllFingerprints[name]; fingerprint != nil {
+		return
+	}
+	return
+}
+
+var QuicPresetFingerprints = map[string]*quic.QUICID {
+	"chrome":     &quic.QUICChrome_115,
+	"firefox":    &quic.QUICFirefox_116,
+	"random":     nil,
+}
+
+var QuicAllFingerprints = map[string]*quic.QUICID {
+	"quicchrome_115":      &quic.QUICChrome_115,
+	"quicchrome_115_ipv4": &quic.QUICChrome_115_IPv4,
+	"quicchrome_115_ipv6": &quic.QUICChrome_115_IPv6,
+	"quicfirefox_116":     &quic.QUICFirefox_116,
+	"quicfirefox_116a":    &quic.QUICFirefox_116A,
+	"quicfirefox_116b":    &quic.QUICFirefox_116B,
+	"quicfirefox_116c":    &quic.QUICFirefox_116C,
+}
+
 var PresetFingerprints = map[string]*utls.ClientHelloID{
 	// Recommended preset options in GUI clients
 	"chrome":     &utls.HelloChrome_Auto,