diff --git a/src/net/fd_unix.go b/src/net/fd_unix.go
index a400c6075e67a206ef9ce46ee67a76f978ce2f6a..198f606284db22e7c523cca1fa8dc56f68627481 100644
--- a/src/net/fd_unix.go
+++ b/src/net/fd_unix.go
@@ -190,6 +190,9 @@ func (fd *netFD) accept() (netfd *netFD, err error) {
 	return netfd, nil
 }
 
+// Defined in os package.
+func newUnixFile(fd uintptr, name string) *os.File
+
 func (fd *netFD) dup() (f *os.File, err error) {
 	ns, call, err := fd.pfd.Dup()
 	if err != nil {
@@ -199,5 +202,5 @@ func (fd *netFD) dup() (f *os.File, err error) {
 		return nil, err
 	}
 
-	return os.NewFile(uintptr(ns), fd.name()), nil
+	return newUnixFile(uintptr(ns), fd.name()), nil
 }
diff --git a/src/net/file_unix_test.go b/src/net/file_unix_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..0a8badf23f00f3719e9483754120cd5804c06d10
--- /dev/null
+++ b/src/net/file_unix_test.go
@@ -0,0 +1,97 @@
+// Copyright 2023 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build unix
+
+package net
+
+import (
+	"internal/syscall/unix"
+	"testing"
+)
+
+// For backward compatibility, opening a net.Conn, turning it into an os.File,
+// and calling the Fd method should return a blocking descriptor.
+func TestFileFdBlocks(t *testing.T) {
+	ls := newLocalServer(t, "unix")
+	defer ls.teardown()
+
+	errc := make(chan error, 1)
+	done := make(chan bool)
+	handler := func(ls *localServer, ln Listener) {
+		server, err := ln.Accept()
+		errc <- err
+		if err != nil {
+			return
+		}
+		defer server.Close()
+		<-done
+	}
+	if err := ls.buildup(handler); err != nil {
+		t.Fatal(err)
+	}
+	defer close(done)
+
+	client, err := Dial(ls.Listener.Addr().Network(), ls.Listener.Addr().String())
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer client.Close()
+
+	if err := <-errc; err != nil {
+		t.Fatalf("server error: %v", err)
+	}
+
+	// The socket should be non-blocking.
+	rawconn, err := client.(*UnixConn).SyscallConn()
+	if err != nil {
+		t.Fatal(err)
+	}
+	err = rawconn.Control(func(fd uintptr) {
+		nonblock, err := unix.IsNonblock(int(fd))
+		if err != nil {
+			t.Fatal(err)
+		}
+		if !nonblock {
+			t.Fatal("unix socket is in blocking mode")
+		}
+	})
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	file, err := client.(*UnixConn).File()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// At this point the descriptor should still be non-blocking.
+	rawconn, err = file.SyscallConn()
+	if err != nil {
+		t.Fatal(err)
+	}
+	err = rawconn.Control(func(fd uintptr) {
+		nonblock, err := unix.IsNonblock(int(fd))
+		if err != nil {
+			t.Fatal(err)
+		}
+		if !nonblock {
+			t.Fatal("unix socket as os.File is in blocking mode")
+		}
+	})
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	fd := file.Fd()
+
+	// Calling Fd should have put the descriptor into blocking mode.
+	nonblock, err := unix.IsNonblock(int(fd))
+	if err != nil {
+		t.Fatal(err)
+	}
+	if nonblock {
+		t.Error("unix socket through os.File.Fd is non-blocking")
+	}
+}
diff --git a/src/os/file_unix.go b/src/os/file_unix.go
index b8c27d8826a834dd9435916dccba9861b9c5e812..25ce83bf9dc3248261a405959ce81287961c9fb4 100644
--- a/src/os/file_unix.go
+++ b/src/os/file_unix.go
@@ -12,6 +12,7 @@ import (
 	"io/fs"
 	"runtime"
 	"syscall"
+	_ "unsafe" // for go:linkname
 )
 
 const _UTIME_OMIT = unix.UTIME_OMIT
@@ -113,6 +114,22 @@ func NewFile(fd uintptr, name string) *File {
 	return f
 }
 
+// net_newUnixFile is a hidden entry point called by net.conn.File.
+// This is used so that a nonblocking network connection will become
+// blocking if code calls the Fd method. We don't want that for direct
+// calls to NewFile: passing a nonblocking descriptor to NewFile should
+// remain nonblocking if you get it back using Fd. But for net.conn.File
+// the call to NewFile is hidden from the user. Historically in that case
+// the Fd method has returned a blocking descriptor, and we want to
+// retain that behavior because existing code expects it and depends on it.
+//
+//go:linkname net_newUnixFile net.newUnixFile
+func net_newUnixFile(fd uintptr, name string) *File {
+	f := newFile(fd, name, kindNonBlock)
+	f.nonblock = true // tell Fd to return blocking descriptor
+	return f
+}
+
 // newFileKind describes the kind of file to newFile.
 type newFileKind int