Build a VPN screen inside an iOS app and the first surprise is that the Settings app protocol list is not the API list. The Network Extension framework lets your app create one personal VPN configuration, owned by that app, and it accepts only a few protocol objects. Everything else people expect, from L2TP to a downloaded profile, lives outside that surface or is private to Apple. The failures that follow are usually not bugs, they are the API refusing a job it was never given.

The myth: the API is a Settings editor

The belief is that because iOS can configure IKEv1, IKEv2, L2TP and IPsec profiles, an app can create any of them programmatically. It cannot. The framework exposes exactly one configuration slot to the calling app, and that slot accepts a small set of public protocol classes. Everything else people expect, from L2TP to a downloaded profile, lives outside that surface or is private to Apple.

The failures that follow are usually not bugs. They are the API refusing a job it was never given, and the fix is to design the screen around what the API owns.

Three protocol objects and the ones that do not exist

Your options are IKEv1 through NEVPNProtocolIPSec, IKEv2 through NEVPNProtocolIKEv2, and NETunnelProviderProtocol if you ship a packet tunnel extension of your own. The NEVPNManager reference lists the accepted protocol objects. L2TP and PPTP never appear in that list because Apple published no classes for them. Attempting either ends at save time with an error, or worse, with a configuration the system quietly declines to use.

Desktop code follows different rules. On macOS, SCNetworkInterfaceCreateWithInterface stacks an interface type such as L2TP over IPv4 and attaches it to a network service, and networksetup -connectpppoeservice starts the result. Administrator rights and a helper tool are required, since the shared secret and password go into the system keychain. Copy that design to iOS and nothing compiles.

Pass a keychain reference, not the password

Neither IKE class accepts a password as text. The two properties passwordReference and sharedSecretReference want a persistent keychain reference, which you obtain from SecItemCopyMatching by asking for kSecReturnPersistentRef. Passing the item data, or casting a string into the field, produces a save failure or a connection with blank credentials. The reference is only as durable as the keychain item behind it, which is why the item has to exist before the configuration is assembled. Create it in the same transaction as the save and you remove a whole class of ordering bugs.

Certificate authentication takes a different route: identityData plus identityDataPassword for a PKCS#12 blob. Shipping that blob inside the app bundle is a habit carried over from sample code rather than a shipping decision, because the password travels with it and every copy of the app carries the same identity.

Entitlement and availability gate everything

The manager may not exist at all, and the reason is provisioning. Your app identifier needs the Network Extension capability together with the personal VPN entitlement, and the profile that signs the build has to carry both.

Skip that and the shared instance returns nil, or the save fails with an error that never mentions entitlement. Availability is the other gate. Target anything older than iOS 8 and the symbol is missing at runtime, so the failure is a crash or a nil manager rather than a manager with no configuration. Check the runtime and the entitlement before debugging your object graph.

Load, modify, save, then watch the on-demand order

Treat the lifecycle as load, modify, save, and never skip the load. Until loadFromPreferences completes, the manager holds nothing and the protocol object cannot be read or swapped.

A second save replaces the configuration instead of adding another, so a half-finished save can damage a profile that was working. On-demand rules form an ordered array, and that order is the policy. Put a connect-anywhere rule ahead of the disconnect rule and a manual disconnect is followed by an immediate reconnect, which reads like a defect but follows the documented evaluation.

Move the disconnect rule to the front when the user action has to win. What remains outside the app's control matters just as much. Routes come from the tunnel or the server policy rather than from a preference you set, with a packet tunnel provider reporting them when it starts. The app cannot list or edit profiles installed by other apps or configuration profiles, so do not promise a screen that shows every VPN on the device.

Ũhoro ũhĩtĩte