SOC 2 Hardening for Self-Hosted Voice AI on Azure: A 2026 Guide

What changed in the jambonz 11.1.6 Terraform modules for Azure, and why each change matters to an auditor

Dave Horton

In our last post, How to build a SOC-2 compliant Voice AI platform on AWS, we described what we learned preparing jambonz.cloud for SOC 2, and how jambonz 11.1.6 builds that learning into our AWS CloudFormation templates. Many of our self-hosted customers run on Azure, so we have now done the same work on our Azure Terraform modules.

This post is the Azure follow-up. It doesn’t repeat the background on SOC 2 or the application-level changes in 11.1.6. Those apply on every platform, and the AWS post covers them. Here we focus on what changed in the Azure modules, and why an auditor would care.

Key Takeaways

What We Changed, and Why It Matters

Network rules that actually restrict traffic

Azure network security groups (NSGs) carry a built-in rule, AllowVnetInBound, that allows all traffic between resources in the same virtual network. Our older modules had rules that looked restrictive, but with that default in place, every VM could reach every port on every other VM. Several internal ports, including the API server’s port 3000, were also open to the internet.

In 11.1.6, on medium and large, each server role has its own NSG. It lists only the ports that role serves and ends with an explicit rule that denies all other traffic from inside the virtual network. Internal ports are no longer reachable from the internet. On mini, port 3000 and Homer (9080) are closed.

Why it matters: an auditor will look at how you restrict access to your systems from outside your boundary (CC6.6), and at whether access inside the network follows least privilege (CC6.1). A rule that the platform silently overrides is hard to defend in an audit, and in an attack a compromised server can’t reach ports that are not open to it.

SSH closed by default

allowed_ssh_cidr no longer defaults to 0.0.0.0/0. It has no default at all: you name your admin IP as a /32, or your VPN range.

Why it matters: limiting administrative access to known sources is one of the first network controls an auditor checks (CC6.1, CC6.6).

No secrets in custom data

The older modules passed the MySQL password, the Redis key, the encryption secret and any API keys to each VM in its custom_data. Anyone with read access to the VM’s configuration in Azure could read them, as could any process on the VM through the instance metadata service. The setup scripts also ran with bash -x, which echoed every command, secrets included, into the setup log on disk.

In 11.1.6 the secrets live in Azure Key Vault. Each VM reads the ones it needs at boot with its managed identity, and the setup scripts no longer echo their commands. On mini, the MySQL password and the encryption secret are generated on the VM itself at first boot, so they never leave it. Any apiban or Krisp keys you supply go in a Key Vault that is created only when you set one.

Why it matters: secrets handling is a core part of logical access control (CC6.1). A secret that sits in plain text in a VM’s configuration or a log file is a finding, and a reasonable auditor will ask who could have read it.

Least-privilege identities

The managed identity used by the VMs held the Network Contributor and Virtual Machine Contributor roles, and nothing in jambonz used them. A process on any VM could have used that identity to change the network or other VMs. In 11.1.6 the identity can read the deployment’s secrets by name, and nothing else.

Why it matters: an auditor may ask you to show that no system holds more access than it needs (CC6.3).

Private Redis

Redis holds call state for the cluster, and in the older modules it was reachable from the public internet, protected only by its access key. In 11.1.6 on medium and large, public network access is disabled, and the virtual network reaches Redis through a private endpoint, over TLS.

Why it matters: this narrows your system boundary (CC6.6) and protects data in transit (CC6.7).

A protected, audited database

On medium and large, MySQL (Azure Database for MySQL Flexible Server) is encrypted at rest, keeps 30 days of backups, logs every connection (logins, logouts and failed logins) to Log Analytics, and carries a CanNotDelete lock, so it can’t be deleted by accident.

Why it matters: this is your evidence for protecting confidential data (C1.1), for backup and recovery (A1.2), and for monitoring access to the database (CC7.2).

Centralized logs with retention

A Log Analytics workspace keeps the system logs (including SSH logins) and the jambonz application logs from every VM and scale set for log_retention_days: 90 days by default, with 180 and 365 available. The Azure Monitor Agent on each VM collects them.

Why it matters: retained, centralized logs are what an auditor samples to confirm that you monitor your systems (CC7.2).

Surviving a zone failure

On medium and large, zones (by default ["1","2","3"]) spreads the SIP and RTP servers (large) or SBCs (medium) across availability zones and balances the feature server and recording scale sets across them. The public IPs and the recording load balancer are zone-redundant, MySQL keeps a standby in a second zone with automatic failover, and Redis keeps a replica in another zone.

Why it matters: if your report includes the Availability category, an auditor will look at how you keep the service running and recover it (A1.2).

Bugs found by testing

We deployed every module on the 11.1.6 images and ran our release test suite against it. That testing found two older bugs in the large module, both now fixed: the recording servers had no outbound internet access, so recordings couldn’t be uploaded, and with more than one RTP server, every call failed.

What These Modules Don’t Do (Yet)

We’d rather tell you now than have your auditor find it:

Deploying

Clone the jambonz Terraform repository, copy the example settings for your size and architecture, and fill in the required values: your subscription and tenant IDs, an SSH public key, the portal’s DNS name and allowed_ssh_cidr.

cd terraform/azure/provision-vm-large
cp terraform.tfvars.example terraform.tfvars
terraform init
terraform plan
terraform apply

The images are published in an Azure community gallery in 30 regions (amd64) and 29 regions (arm64). Note that the zone-redundant defaults add cost: two SIP and two RTP servers (or two SBCs), two feature servers, and a General Purpose MySQL SKU, because zone-redundant MySQL doesn’t support Burstable SKUs. If you are updating an existing deployment, read the 11.1.6 release notes first: moving to availability zones replaces the public IPs, so the SBC addresses your carriers allow-list will change.

What Remains Yours

These controls sit at the subscription or company level, and an auditor will ask about them too:

Get Started

jambonz 11.1.6 for Azure is available now. Start with the Azure self-hosting guide, read the AWS post for the application-level changes in this release, or contact us about running jambonz in your environment.

SOC 2 Criteria Referenced in This Post

The summaries below are our own wording. The official text is in the AICPA’s 2017 Trust Services Criteria (With Revised Points of Focus – 2022).

Frequently asked questions

Does deploying jambonz on Azure with these Terraform modules make my company SOC 2 compliant?

No. SOC 2 is a report on your organization's controls, and no software can produce it for you. The 11.1.6 Azure modules take care of infrastructure controls an auditor will test on your voice platform, such as secrets handling, least-privilege access, network restrictions and log retention. Your identity and access management, your subscription-wide logging, your change management and your written policies are still yours.

Where does call data live when I self-host jambonz on Azure?

In your own Azure subscription, in a resource group and virtual network you control. Recordings, call records, logs and secrets stay under your Microsoft Entra ID (formerly Azure Active Directory) access policies and your Azure Activity Log.

Which Azure deployment sizes include the SOC 2 hardening?

All three Terraform modules (mini, medium and large) close SSH by default, keep secrets out of custom data and send logs to Log Analytics with a retention period. Medium and large also spread every tier across availability zones and add per-role network security groups, a private Redis endpoint and MySQL connection auditing.