diff --git a/net/dns/direct.go b/net/dns/direct.go
index 31613902a..c9081b16e 100644
--- a/net/dns/direct.go
+++ b/net/dns/direct.go
@@ -9,8 +9,6 @@ package dns
 import (
 	"bufio"
 	"bytes"
-	"errors"
-	"fmt"
 	"io"
 	"io/ioutil"
 	"os"
@@ -23,7 +21,6 @@ import (
 )
 
 const (
-	tsConf     = "/etc/resolv.tailscale.conf"
 	backupConf = "/etc/resolv.pre-tailscale-backup.conf"
 	resolvConf = "/etc/resolv.conf"
 )
@@ -47,11 +44,10 @@ func writeResolvConf(w io.Writer, servers []netaddr.IP, domains []string) {
 	}
 }
 
-// readResolvConf reads DNS configuration from /etc/resolv.conf.
-func readResolvConf() (OSConfig, error) {
+func readResolvFile(path string) (OSConfig, error) {
 	var config OSConfig
 
-	f, err := os.Open("/etc/resolv.conf")
+	f, err := os.Open(path)
 	if err != nil {
 		return config, err
 	}
@@ -82,6 +78,11 @@ func readResolvConf() (OSConfig, error) {
 	return config, nil
 }
 
+// readResolvConf reads DNS configuration from /etc/resolv.conf.
+func readResolvConf() (OSConfig, error) {
+	return readResolvFile(resolvConf)
+}
+
 // isResolvedRunning reports whether systemd-resolved is running on the system,
 // even if it is not managing the system DNS settings.
 func isResolvedRunning() bool {
@@ -114,46 +115,72 @@ func newDirectManager() directManager {
 	return directManager{}
 }
 
-func (m directManager) SetDNS(config OSConfig) error {
-	// Write the tsConf file.
-	buf := new(bytes.Buffer)
-	writeResolvConf(buf, config.Nameservers, config.SearchDomains)
-	if err := atomicfile.WriteFile(tsConf, buf.Bytes(), 0644); err != nil {
+// ownedByTailscale reports whether /etc/resolv.conf seems to be a
+// tailscale-managed file.
+func (m directManager) ownedByTailscale() (bool, error) {
+	st, err := os.Stat(resolvConf)
+	if err != nil {
+		if os.IsNotExist(err) {
+			return false, nil
+		}
+		return false, err
+	}
+	if !st.Mode().IsRegular() {
+		return false, nil
+	}
+	bs, err := ioutil.ReadFile(resolvConf)
+	if err != nil {
+		return false, err
+	}
+	if bytes.Contains(bs, []byte("generated by tailscale")) {
+		return true, nil
+	}
+	return false, nil
+}
+
+// backupConfig creates or updates a backup of /etc/resolv.conf, if
+// resolv.conf does not currently contain a Tailscale-managed config.
+func (m directManager) backupConfig() error {
+	if _, err := os.Stat(resolvConf); err != nil {
+		if os.IsNotExist(err) {
+			// No resolv.conf, nothing to back up. Also get rid of any
+			// existing backup file, to avoid restoring something old.
+			os.Remove(backupConf)
+			return nil
+		}
 		return err
 	}
 
-	if linkPath, err := os.Readlink(resolvConf); err != nil {
-		// Remove any old backup that may exist.
-		os.Remove(backupConf)
-
-		// Backup the existing /etc/resolv.conf file.
-		contents, err := ioutil.ReadFile(resolvConf)
-		// If the original did not exist, still back up an empty file.
-		// The presence of a backup file is the way we know that Up ran.
-		if err != nil && !errors.Is(err, os.ErrNotExist) {
-			return err
-		}
-		if err := atomicfile.WriteFile(backupConf, contents, 0644); err != nil {
-			return err
-		}
-	} else if linkPath != tsConf {
-		// Backup the existing symlink.
-		os.Remove(backupConf)
-		if err := os.Symlink(linkPath, backupConf); err != nil {
-			return err
-		}
-	} else {
-		// Nothing to do, resolvConf already points to tsConf.
+	owned, err := m.ownedByTailscale()
+	if err != nil {
+		return err
+	}
+	if owned {
 		return nil
 	}
 
-	os.Remove(resolvConf)
-	if err := os.Symlink(tsConf, resolvConf); err != nil {
+	return os.Rename(resolvConf, backupConf)
+}
+
+func (m directManager) SetDNS(config OSConfig) error {
+	if err := m.backupConfig(); err != nil {
 		return err
 	}
 
+	buf := new(bytes.Buffer)
+	writeResolvConf(buf, config.Nameservers, config.SearchDomains)
+	if err := atomicfile.WriteFile(resolvConf, buf.Bytes(), 0644); err != nil {
+		return err
+	}
+
+	// We might have taken over a configuration managed by resolved,
+	// in which case it will notice this on restart and gracefully
+	// start using our configuration. This shouldn't happen because we
+	// try to manage DNS through resolved when it's around, but as a
+	// best-effort fallback if we messed up the detection, try to
+	// restart resolved to make the system configuration consistent.
 	if isResolvedRunning() {
-		exec.Command("systemctl", "restart", "systemd-resolved.service").Run() // Best-effort.
+		exec.Command("systemctl", "restart", "systemd-resolved.service").Run()
 	}
 
 	return nil
@@ -164,27 +191,53 @@ func (m directManager) SupportsSplitDNS() bool {
 }
 
 func (m directManager) GetBaseConfig() (OSConfig, error) {
-	return OSConfig{}, ErrGetBaseConfigNotSupported
+	owned, err := m.ownedByTailscale()
+	if err != nil {
+		return OSConfig{}, err
+	}
+	fileToRead := resolvConf
+	if owned {
+		fileToRead = backupConf
+	}
+
+	return readResolvFile(fileToRead)
 }
 
 func (m directManager) Close() error {
+	// We used to keep a file for the tailscale config and symlinked
+	// to it, but then we stopped because /etc/resolv.conf being a
+	// symlink to surprising places breaks snaps and other sandboxing
+	// things. Clean it up if it's still there.
+	os.Remove("/etc/resolv.tailscale.conf")
+
 	if _, err := os.Stat(backupConf); err != nil {
-		// If the backup file does not exist, then Up never ran successfully.
 		if os.IsNotExist(err) {
+			// No backup, nothing we can do.
 			return nil
 		}
 		return err
 	}
-
-	if ln, err := os.Readlink(resolvConf); err != nil {
+	owned, err := m.ownedByTailscale()
+	if err != nil {
 		return err
-	} else if ln != tsConf {
-		return fmt.Errorf("resolv.conf is not a symlink to %s", tsConf)
 	}
+	_, err = os.Stat(resolvConf)
+	if err != nil && !os.IsNotExist(err) {
+		return err
+	}
+	resolvConfExists := !os.IsNotExist(err)
+
+	if resolvConfExists && !owned {
+		// There's already a non-tailscale config in place, get rid of
+		// our backup.
+		os.Remove(backupConf)
+		return nil
+	}
+
+	// We own resolv.conf, and a backup exists.
 	if err := os.Rename(backupConf, resolvConf); err != nil {
 		return err
 	}
-	os.Remove(tsConf)
 
 	if isResolvedRunning() {
 		exec.Command("systemctl", "restart", "systemd-resolved.service").Run() // Best-effort.