diff --git a/ipn/ipn_clone.go b/ipn/ipn_clone.go
index 34b7bc5a7..8f9bc0567 100644
--- a/ipn/ipn_clone.go
+++ b/ipn/ipn_clone.go
@@ -24,10 +24,7 @@ func (src *Prefs) Clone() *Prefs {
 	*dst = *src
 	dst.AdvertiseTags = append(src.AdvertiseTags[:0:0], src.AdvertiseTags...)
 	dst.AdvertiseRoutes = append(src.AdvertiseRoutes[:0:0], src.AdvertiseRoutes...)
-	if dst.Persist != nil {
-		dst.Persist = new(persist.Persist)
-		*dst.Persist = *src.Persist
-	}
+	dst.Persist = src.Persist.Clone()
 	return dst
 }
 
diff --git a/ipn/ipn_view.go b/ipn/ipn_view.go
index 1cfa0eee9..2209cb0a1 100644
--- a/ipn/ipn_view.go
+++ b/ipn/ipn_view.go
@@ -87,13 +87,7 @@ func (v PrefsView) NoSNAT() bool                          { return v.ж.NoSNAT }
 func (v PrefsView) NetfilterMode() preftype.NetfilterMode { return v.ж.NetfilterMode }
 func (v PrefsView) OperatorUser() string                  { return v.ж.OperatorUser }
 func (v PrefsView) ProfileName() string                   { return v.ж.ProfileName }
-func (v PrefsView) Persist() *persist.Persist {
-	if v.ж.Persist == nil {
-		return nil
-	}
-	x := *v.ж.Persist
-	return &x
-}
+func (v PrefsView) Persist() persist.PersistView          { return v.ж.Persist.View() }
 
 // A compilation failure here means this code must be regenerated, with the command at the top of this file.
 var _PrefsViewNeedsRegeneration = Prefs(struct {
diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go
index c1ebef7a6..13a71cfbf 100644
--- a/ipn/ipnlocal/local.go
+++ b/ipn/ipnlocal/local.go
@@ -517,7 +517,7 @@ func (b *LocalBackend) Shutdown() {
 }
 
 func stripKeysFromPrefs(p ipn.PrefsView) ipn.PrefsView {
-	if !p.Valid() || p.Persist() == nil {
+	if !p.Valid() || !p.Persist().Valid() {
 		return p
 	}
 
@@ -816,7 +816,7 @@ func (b *LocalBackend) setClientStatus(st controlclient.Status) {
 	b.mu.Lock()
 
 	if st.LogoutFinished != nil {
-		if p := b.pm.CurrentPrefs(); p.Persist() == nil || p.Persist().LoginName == "" {
+		if p := b.pm.CurrentPrefs(); !p.Persist().Valid() || p.Persist().LoginName() == "" {
 			b.mu.Unlock()
 			return
 		}
@@ -1203,7 +1203,7 @@ func (b *LocalBackend) Start(opts ipn.Options) error {
 	if opts.UpdatePrefs != nil {
 		oldPrefs := b.pm.CurrentPrefs()
 		newPrefs := opts.UpdatePrefs.Clone()
-		newPrefs.Persist = oldPrefs.Persist()
+		newPrefs.Persist = oldPrefs.Persist().AsStruct()
 		pv := newPrefs.View()
 		if err := b.pm.SetPrefs(pv); err != nil {
 			b.logf("failed to save UpdatePrefs state: %v", err)
@@ -1228,7 +1228,7 @@ func (b *LocalBackend) Start(opts ipn.Options) error {
 	b.applyPrefsToHostinfoLocked(hostinfo, prefs)
 
 	b.setNetMapLocked(nil)
-	persistv := prefs.Persist()
+	persistv := prefs.Persist().AsStruct()
 	if persistv == nil {
 		persistv = new(persist.Persist)
 	}
@@ -1947,8 +1947,8 @@ func (b *LocalBackend) initMachineKeyLocked() (err error) {
 	}
 
 	var legacyMachineKey key.MachinePrivate
-	if p := b.pm.CurrentPrefs().Persist(); p != nil {
-		legacyMachineKey = p.LegacyFrontendPrivateMachineKey
+	if p := b.pm.CurrentPrefs().Persist(); p.Valid() {
+		legacyMachineKey = p.LegacyFrontendPrivateMachineKey()
 	}
 
 	keyText, err := b.store.ReadState(ipn.MachineKeyStateKey)
@@ -2481,7 +2481,7 @@ func (b *LocalBackend) setPrefsLockedOnEntry(caller string, newp *ipn.Prefs) ipn
 
 	oldp := b.pm.CurrentPrefs()
 	if oldp.Valid() {
-		newp.Persist = oldp.Persist().Clone() // caller isn't allowed to override this
+		newp.Persist = oldp.Persist().AsStruct() // caller isn't allowed to override this
 	}
 	// findExitNodeIDLocked returns whether it updated b.prefs, but
 	// everything in this function treats b.prefs as completely new
@@ -3338,7 +3338,7 @@ func (b *LocalBackend) hasNodeKey() bool {
 	b.mu.Lock()
 	defer b.mu.Unlock()
 	p := b.pm.CurrentPrefs()
-	return p.Valid() && p.Persist() != nil && !p.Persist().PrivateNodeKey.IsZero()
+	return p.Valid() && p.Persist().Valid() && !p.Persist().PrivateNodeKey().IsZero()
 }
 
 // nextState returns the state the backend seems to be in, based on
@@ -3927,7 +3927,7 @@ func (b *LocalBackend) SetDNS(ctx context.Context, name, value string) error {
 	b.mu.Lock()
 	cc := b.ccAuto
 	if prefs := b.pm.CurrentPrefs(); prefs.Valid() {
-		req.NodeKey = prefs.Persist().PrivateNodeKey.Public()
+		req.NodeKey = prefs.Persist().PrivateNodeKey().Public()
 	}
 	b.mu.Unlock()
 	if cc == nil {
diff --git a/types/persist/persist.go b/types/persist/persist.go
index 38f18baff..bca4b0e6b 100644
--- a/types/persist/persist.go
+++ b/types/persist/persist.go
@@ -45,7 +45,7 @@ type Persist struct {
 	// this node will not operate network lock on. This is used to
 	// prevent bootstrapping TKA onto a key authority which was forcibly
 	// disabled.
-	DisallowedTKAStateIDs []string
+	DisallowedTKAStateIDs []string `json:",omitempty"`
 }
 
 // PublicNodeKey returns the public key for the node key.