Installing Surface Security on Azure
This guide walks you through deploying Surface Security into your Azure tenant from Azure Marketplace.
Prerequisites
- Azure subscription with Owner role on the subscription (required to create role assignments during deploy), OR Contributor role on a pre-created resource group plus User Access Administrator role on the subscription
- Domain you control with the ability to create DNS records (Azure DNS, Cloudflare, Route53, etc.) — optional for an initial trial, see §6
- TLS certificate for your domain as a PFX file — encrypted (password-protected) or not, both are supported. It must (1) contain the full chain (your certificate plus any intermediate CA certificates), (2) be issued by a publicly trusted CA, and (3) include your custom domain in the Subject Alternative Name (SAN). If the PFX is encrypted, its passphrase must be alphanumeric only. See §2.
- Surface Security license key from your Azure Marketplace purchase confirmation / portal status page — you paste this during the deploy (it licenses the deployment and authorizes the container image pull). There is no separate ACR token to copy.
- Access to an Azure region where the Standard_Dxs_v4 / Standard_Exds_v4 VM families and Azure Container Instances are available (the installer runs in ACI; see §Known limitations)
Cost estimate
The managed application provisions Azure infrastructure into your subscription; Microsoft bills you for that infrastructure directly. Surface Security's software licence is a separate line item delivered via Marketplace (zero charge on * plans, support uplift on *-supported).
Indicative monthly Azure infrastructure cost at retail (East US, on-demand pricing, May 2026):
| Tier | Azure infra | Top drivers |
|---|---|---|
| Small | ~$1,600 / month | AKS ClickHouse pool ($420), App Gateway WAF_v2 ($295), Log Analytics ingest + retention ($375), AKS system pool ($280) |
| Medium | ~$4,300 / month | Log Analytics ingest + retention ($1,410), AKS pools ($1,680), PostgreSQL with HA ($520 compute + $156 storage/backup), App Gateway WAF_v2 ($295) |
| Large | ~$13,500 / month | Log Analytics ingest + retention ($5,640), AKS pools ($4,485), PostgreSQL with HA ($2,079 compute + $625 storage/backup), egress ($261) |
What's included: AKS (system + dedicated ClickHouse node pool), PostgreSQL Flexible Server (HA on Medium/Large), Application Gateway v2 with WAF, public IP, Log Analytics workspace with 730-day retention, Application Insights, Storage Account (blob ZRS), Key Vault, VNet, private endpoints, private DNS zones, managed disks, and outbound egress.
Major cost drivers to be aware of:
- Log Analytics 730-day retention — required to meet PCI DSS, FedRAMP, and CJIS audit retention. At steady state retention costs roughly match ingest costs. If your compliance regime allows shorter retention, contact support to discuss tuning
infra/azure-managed-app/src/modules/monitoring.bicepfor a private offer. - App Gateway WAF_v2 fixed cost — runs ~$263 / month flat regardless of tier (autoscale CUs add ~$30 / month on top).
- PostgreSQL HA — Medium and Large run zone-redundant standby (doubles compute cost) for SOC 2 A1.2 and HIPAA contingency planning. Small is Burstable (no HA available on Burstable SKUs in Azure).
- ClickHouse node pool — dedicated
E*ds_v4memory-optimised VM; sized for analytical query latency, not throughput. Cannot be co-located with the system pool without disk I/O contention.
What you can do to reduce cost:
- Reserved Instances — committing to 1-year or 3-year reservations on the AKS VM SKUs cuts the AKS compute line ~30-60%. The VM SKUs eligible for RIs are
Standard_D4s_v4,Standard_D8s_v4,Standard_D16s_v4,Standard_E8ds_v4,Standard_E16ds_v4,Standard_E32ds_v4. - Azure Hybrid Benefit — does not apply (we run Linux only).
- Enterprise Agreement / MCA discounts — your tenant's negotiated rates apply automatically; the totals above use public retail pricing.
The numbers above are produced by infra/azure-managed-app/scripts/estimate-cost.ps1, which queries the Azure Retail Prices API live and parses the tier sizing from src/modules/common.bicep. They exclude:
- Surface Security's own software fees (handled separately via Marketplace plan)
- Bandwidth from your endpoints uploading telemetry (paid by your endpoint subscriptions, not the deployment subscription)
- Microsoft Defender for Cloud, Azure Monitor alerts, Backup vaults, or other Azure-add-on services you may already have policy-mandated to add
- Marketplace plan fees for
*-supportedvariants
Rerun the script before any sizing conversation to refresh against current pricing.
Deployment flow
1. Get your license key
After subscribing on Azure Marketplace, you land on your Surface Security portal status page:
https://portal.surfacesecurity.dev/marketplace/azure/status/<your-token>
Copy your license key from this page — you paste it into the License step of the deploy form (step 4). The deployment registers this license with the Surface Security portal to obtain time-limited credentials for pulling the product container images, and the deployment is licensed automatically. You do not need to copy any registry/ACR token separately.
2. Prepare your TLS certificate
You provide your certificate as a .pfx file and upload it directly in the deploy form — the
portal encodes it for you (no manual base64). Encrypted (password-protected) PFX files are fully
supported; just enter the passphrase in the form.
Your certificate must meet three requirements, or the site will deploy but fail its health check:
- Full chain — the
.pfxmust contain your leaf certificate and any intermediate CA certificates. Most public CAs issue an intermediate; include it. - Publicly trusted CA with your custom domain in the Subject Alternative Name (SAN) — the same certificate is presented internally and validated by the gateway, so a self-signed or private-CA certificate will not work. Why: both the App Gateway backend health probe and the in-product first-run setup probe (step 7) open a real TLS connection that is validated against the default public trust store. An internal/enterprise-CA certificate would require a custom CA bundle distributed into the api pods and the gateway backend trust, which the standard package does not wire up (that is private-plan territory) — contact support if you must use a private CA.
- If the PFX is encrypted, its passphrase must be alphanumeric only.
If your issuer delivered the certificate and key separately, bundle them into a full-chain PFX:
# leaf + intermediate(s) in fullchain.pem, private key in privkey.pem
openssl pkcs12 -export -out cert.pfx -inkey privkey.pem -in fullchain.pem -certfile intermediates.pem
# (set a passphrase if you wish -- alphanumeric only -- or press enter for none)
That cert.pfx is what you upload in step 4. No base64 conversion is required.
3. Click "Deploy to Azure"
The button on the status page opens the Azure portal create blade for the managed application.
4. Fill in the form
Basics:
- Subscription — which Azure subscription gets billed
- Resource group — create a new one, e.g.
surfacesec-prod-rg. Use a fresh resource group name; if you have previously installed and uninstalled Surface Security, see Known limitations §Key Vault 7-day hold before reusing a prior RG name - Region — pick a region where the
Standard_Dxs_v4VM family and Azure Container Instances are available for your subscription (most regions; verify the VM SKU withaz vm list-skus --location <region> --size Standard_D4s_v4— an emptyrestrictionsarray means available). The default deploys a zone-redundant configuration, so prefer a region with availability zones. - Administrator email — ops contact for AKS alerts and deployment notifications
- Customer email — where we deliver your license + deployment status link
License:
- License key — paste the key from your portal status page (step 1). The deployment registers it with the Surface Security portal to pull the product images and to license the cluster.
Sizing:
- Deployment tier — pick based on your expected endpoint count. The dropdown labels include endpoint caps and an indicative monthly Azure infrastructure cost (Small ≤5k ~$1,600/mo, Medium ≤25k ~$4,300/mo, Large ≤100k ~$13,500/mo). See Cost estimate for the full breakdown. You can upgrade to a larger tier later without reinstalling.
Networking:
- Custom domain (FQDN) — the domain Surface Security will run on, e.g.
security.yourcompany.com. You will point DNS at the deployment output IP in step 6. (You can also reach the deployment immediately via the auto-assigned*.cloudapp.azure.comaddress — see step 6.)
TLS certificate:
- TLS certificate (.pfx) — click browse and select your
.pfxfile from step 2. The portal encodes it automatically. - PFX passphrase — enter your certificate's passphrase if it is encrypted (alphanumeric only); leave both fields blank only if your PFX has no password.
PostgreSQL admin credentials:
- Username — default
surfaceadminis fine, or choose your own (3-63 lowercase letters/digits/underscores) - Password — minimum 12 chars with uppercase, lowercase, digit, and symbol. Store securely; you will need this for any future direct database maintenance.
Review + create. Deploy times by tier:
- Small: ~15-25 minutes (AKS cluster creation dominates)
- Medium: ~25-40 minutes
- Large: ~35-55 minutes
5. Confirm deploy succeeded
After the deployment reaches Succeeded state, the managed-application's Outputs blade shows:
| Output | Meaning |
|---|---|
customerUrl | The URL customers (and you) will navigate to — you'll configure DNS next |
appgwPublicIp | The public IP address of your Application Gateway — target your DNS at this |
aksClusterName | Name of the AKS cluster (visible in the managed resource group) |
keyVaultName | Name of the Key Vault holding your cert and DB secrets |
pgFqdn | PostgreSQL server FQDN (only reachable via the managed VNet) |
appgwManagedFqdn | Azure-assigned *.cloudapp.azure.com address — reach the deployment before delegating your own DNS |
If the deployment fails, see Troubleshooting.
6. Point your domain at the deployed public IP
Application Gateway does not issue or validate certificates and does not manage DNS — you bring your own certificate (step 2) and point your domain at the gateway. You have three options:
- Production (recommended): create an A record for your custom domain pointing at
appgwPublicIp:Verify withsecurity.yourcompany.com. 300 IN A <appgwPublicIp>dig security.yourcompany.com +short(should return the public IP). Wait for your DNS TTL if the record was previously set elsewhere. - No-DNS quick start (trials): browse straight to the
appgwManagedFqdnoutput (https://<label>.<region>.cloudapp.azure.com). The deployment is reachable immediately, but your uploaded certificate won't match this host, so your browser shows a TLS name-mismatch warning — expected; use it only for early validation. You cannot complete the first-run setup wizard (step 7) over the*.cloudapp.azure.comhost: the wizard runs a strict DNS + trusted-TLS + instance-identity probe of the URL you enter, which this address fails (the certificate is for your custom domain). Finish the real custom-domain A record + trusted certificate + DNS propagation first, then run the wizard againsthttps://<your-domain>. - Subdomain delegation (cleanest for enterprises): use a dedicated subdomain such as
surface.yourcompany.comand delegate that child zone (or just add the A record) so you never touch your apex.
7. Complete the setup wizard
Before you start, confirm DNS + TLS are live for your custom domain:
dig <your-domain> +shortreturns the gateway public IP (appgwPublicIp), andhttps://<your-domain>loads with a valid certificate (no name-mismatch or untrusted-CA warning).
The wizard verifies that the Site URL you enter resolves and points back at this instance over
trusted TLS — so this must succeed first. (You cannot use the *.cloudapp.azure.com address for
this step; see step 6.)
Navigate to https://<your-domain>. Because the deployment is already licensed (the license key you
entered at deploy time was registered with the portal), the wizard goes straight to onboarding:
- Create your first admin user (email + password)
- Review default policies (you can customize later)
- Confirm setup
If the wizard reports externalBaseUrl is unreachable or "the URL points at a different
Surface instance", your DNS hasn't propagated yet or the certificate isn't trusted/served — wait
for DNS propagation (up to your TTL) and re-verify the two checks above, then retry.
8. Deploy the browser extension to your endpoints
See the main Surface Security documentation for extension deployment (Intune, Group Policy, MDM, etc.). Each endpoint auto-enrolls against your domain using the CA cert delivered through the extension config.
Support model
Surface Security Marketplace plans come in two variants per tier:
<tier>(e.g.small,medium,large) — self-managed. No publisher access to your tenant resources. You operate everything; support is email-only.<tier>-supported(e.g.small-supported) — Publisher Management with JIT access enabled. When you open a support ticket, our engineers can request time-bounded elevated access to your managed resource group. All elevation requests are logged in your Azure Activity Log and require each engineer to be approved at the subscription level.
You choose which variant when subscribing on Marketplace. The license key encodes this choice, and the choice is enforced both by Azure Partner Center RBAC and in-product by Surface Security's backend.
Updates
Patch updates (bug fixes, minor features)
Automatic, zero downtime, no action required from you. Your deployment heartbeats to the Surface Security portal every 5 minutes; when a new patch version is available, the in-cluster update controller performs a rolling helm upgrade --atomic with automatic rollback on any health-check failure. Your pods maintain maxUnavailable=0 and a PodDisruptionBudget of minAvailable=2 throughout.
Patch notes appear in your admin dashboard under "Update history".
Major version updates (breaking changes, new infrastructure)
Azure Marketplace does not support in-place upgrade of managed applications. When we release a major version (e.g., v1.x → v2.0) that requires ARM template changes, you will:
- Receive an email notification 30 days ahead of the required upgrade window
- Export your tenant state (policies, users, alert history) via the admin dashboard's "Export" function
- On your chosen upgrade date:
- Uninstall the current managed application
- Wait 7 days for Azure Key Vault soft-delete retention to expire (or deploy the new version into a fresh resource group name to bypass — see §Known limitations)
- Install the new managed application from Marketplace
- Import your tenant state via "Import" in the new admin dashboard
Customers on *-supported plans can request assisted migration — our engineers schedule the cutover with you, handle export/import, and minimize downtime (typically <1 hour).
Major upgrades are rare (expect 6-12 months between them). Patch and minor updates are always in-place.
Renewing / rotating your TLS certificate
Your certificate is stored in your deployment's Key Vault, and Application Gateway re-checks Key Vault for a newer version roughly every 4 hours — so most renewals require no redeployment:
- Preferred: import the renewed certificate as a new version of the
customer-certcertificate in your deployment's Key Vault. Application Gateway picks it up automatically within ~4 hours (no downtime). On*-supportedplans we can do this for you under JIT access. - Alternative: re-run the managed-application deployment (stack update) with the new
.pfx— this re-imports the certificate and refreshes the internal copy as well.
Make sure the renewed certificate keeps the same requirements as step 2 (full chain, public CA, SAN matching your domain).
Uninstalling
Delete the managed application from Azure Portal (Managed Applications blade → your app → Delete).
- The managed resource group and all contained Azure resources are removed
- The Azure Kubernetes Service (AKS) cluster's auto-created
MC_*node resource group is also deleted as a cascade - Your Azure subscription stops accruing charges for these resources immediately
- The Marketplace subscription remains active until you also cancel it (Managed Applications blade does not auto-cancel the subscription)
To fully terminate the Surface Security subscription and billing, also go to Azure Portal → Subscriptions → your Marketplace subscription → Cancel.
Tier changes
- Upgrade (small → medium, medium → large): re-deploy the managed application from Marketplace with the higher-tier plan SKU. During the re-deploy ARM will resize the AKS node pool, bump PostgreSQL compute SKU, and expand the ClickHouse node's premium SSD. Customer data is preserved because storage backends (PG Flex Server, Storage Account, ClickHouse PV) survive the compute resize.
- Downgrade (large → medium, etc.): destructive — requires tenant export, uninstall, reinstall at lower tier, import. Contact support on
*-supportedplans for assisted downgrade.
Known limitations
Key Vault 7-day hold on reinstall
Azure Key Vault has soft-delete with purge-protection enabled (a security best practice we ship with). If you uninstall the managed application and try to reinstall into the same resource group name within 7 days, deployment fails with VaultAlreadyExists.
Workarounds:
- Wait 7 days — Azure auto-purges the soft-deleted vault after the retention period
- Deploy into a different resource group name — the vault name is derived from the resource-group ID, so a new RG name produces a new vault name
SKU availability varies by subscription
The default VM SKUs (Standard_D4s_v4, etc.) are broadly available but not universally. Pay-as-you-go and sponsored subscriptions in some regions may have v4 restricted. Pre-check with:
az vm list-skus --location <your-region> --size Standard_D4s_v4 -o table
az vm list-skus --location <your-region> --size Standard_E8ds_v4 -o table
If either command returns a restriction with NotAvailableForSubscription, contact Azure support to request SKU access, or pick a different region. EastUS2, WestUS2, WestUS3, CentralUS, and North Europe typically have the broadest availability.
TLS certificate requirements
Your .pfx may be encrypted or not. It must contain the full chain (leaf + intermediates), be
issued by a publicly trusted CA, and include your domain in the SAN. If encrypted, the
passphrase must be alphanumeric only. A self-signed or private-CA certificate, or a leaf-only
PFX missing its intermediates, will deploy but fail the gateway's backend health check (see
Troubleshooting). Why a publicly trusted CA is required: the App Gateway backend probe and
the in-product first-run setup probe (step 7) each open a real TLS connection validated against the
default public trust store; an internal/enterprise-CA certificate would need a custom CA bundle
distributed into the api pods and the gateway backend trust, which the standard package does not
wire up (private-plan territory) — contact support. The certificate is imported into your
deployment's Key Vault and rotates automatically — see Renewing / rotating your TLS
certificate.
AKS node resource group is customer-accessible
Azure's managed-app "restrict with deny assignments" covers the top-level managed resource group, but the AKS cluster creates a secondary MC_<managed-rg>_<cluster>_<region> resource group for its VMSS nodes, NICs, and load balancer. This secondary RG does NOT inherit deny assignments — you have full access to those resources, and node-resource-group lockdown is not enabled.
Surface Security's design mitigates this by never writing secrets to node disks. All secrets flow through Azure Key Vault via the Secret Store CSI driver, using federated workload-identity tokens that live only in pod memory. The cluster itself runs with a private API server, Microsoft Entra authentication, and local admin accounts disabled.
Troubleshooting
Deployment failed at the aks module
Common causes:
- K8s version unsupported in your region: Our default is 1.34. If your region is behind, specify a newer version available there via the optional
kubernetesVersionparameter. - VM SKU restricted: See §SKU availability above.
- Out of vCPU quota: Azure portal → Subscription → Usage + quotas. Request an increase for the VM family matching your tier (e.g.,
Standard Dsv4 Family vCPUs).
Deployment failed at the postgres module
LocationIsOfferRestricted: Azure Database for PostgreSQL Flexible Server isn't available for your subscription in the chosen region. Pick a different region or contact Azure support to lift the restriction.
Deployment failed at certificate import or the appgw module
- Certificate import failed / wrong passphrase: confirm the passphrase you entered matches the
.pfx, and that it is alphanumeric only. Re-run the deployment with the correct passphrase. SslCertificatePasswordIncorrect: the supplied passphrase didn't decrypt the PFX, or it contains non-alphanumeric characters. Re-export with an alphanumeric passphrase (or none) and re-deploy. (You do not need a password-less PFX — encrypted PFX files are supported.)
Deployment failed at image pull / Helm
- Image pull / registration failure: confirm your license key is correct and current. The deployment registers it with the Surface Security portal to obtain image-pull credentials; an expired or mistyped key fails here.
Deployment succeeded but https://<your-domain> returns a connection error or 502
- DNS not propagated:
dig <your-domain>— ensure the A record matchesappgwPublicIp. Wait up to your TTL. (Or test via theappgwManagedFqdn*.cloudapp.azure.comaddress.) - 502 / backend unhealthy — certificate problem: this is the most common first-deploy issue. Your certificate must be full-chain (leaf + intermediates), from a publicly trusted CA, with your domain in the SAN. A leaf-only, self-signed, or private-CA certificate fails the gateway's internal HTTPS health check. Re-export a full-chain public-CA PFX and re-deploy. Check Azure Portal → Application Gateway → Backend health.
- Pods still warming up: on the Large tier the cluster takes a few minutes; wait 2-5 minutes and retry.
Browser extension can't enroll
- CA cert mismatch: the extension pins the CA cert you uploaded. If you rotated the cert, re-distribute the new extension config to endpoints.
- Firewall blocking outbound HTTPS: endpoints must reach
https://<your-domain>over port 443.
Getting help
- Documentation: docs.surfacesecurity.dev (public docs site)
- Self-managed plans: email
support@surfacesecurity.dev— response within 24 business hours *-supportedplans: emailsupport@surfacesecurity.devwith[PRIORITY]in subject — response within 4 business hours, with JIT-access option for complex issues- Critical production incident: emergency hotline published on the portal status page for
*-supportedcustomers