Artificial Intelligence (AI) AWS

Presidio PII Detection and Anonymization on AWS User Guide

| Product: Presidio PII Detection and Anonymization

Overview

Presidio is an open source toolkit for finding and removing personally identifiable information (PII) from free text. It runs as two cooperating services: the analyzer, which locates entities such as names, email addresses, phone numbers, credit card numbers and national identifiers and returns the entity type, the exact character offsets and a confidence score for each; and the anonymizer, which redacts, replaces, masks, hashes or encrypts those spans. Presidio was originally created at Microsoft and is now community-governed under the Data Privacy Stack organisation, and is released under the MIT licence.

The cloudimg image installs both services into a Python virtual environment at /opt/presidio, runs each under gunicorn as an unprivileged presidio system account bound to loopback, fronts them with a single nginx gateway on TCP 80 that enforces HTTP Basic authentication on every API path, and generates a unique API password on the first boot of every instance. Backed by 24/7 cloudimg support.

What is included:

  • presidio-analyzer and presidio-anonymizer, both MIT licensed
  • spaCy with the en_core_web_lg language model, MIT licensed and verified against the artifact at build time
  • nginx as an authenticating reverse proxy — the only network-reachable surface
  • A per-instance API password generated on first boot into a root-only file
  • An unauthenticated /healthz endpoint for load-balancer probes

Presidio detecting PII: a POST to /analyze returns the entity type, character offsets and a confidence score for the name, email address, credit card number and phone number in the submitted text

Key facts

Item Value
Platform Ubuntu 24.04 LTS
Install root /opt/presidio
Credentials file /root/presidio-credentials.txt
API user admin
Analyzer (loopback) 127.0.0.1:5001
Anonymizer (loopback) 127.0.0.1:5002
Public API TCP 80 via nginx (HTTP Basic auth)
Health probe GET /healthz (unauthenticated)

Connecting to your instance

Open port 80 (HTTP API) and, if you want shell access, port 22 (SSH) in the instance security group. SSH in as the default login user for the OS variant you launched:

OS variant SSH login user Example
Ubuntu 24.04 LTS ubuntu ssh ubuntu@<public-ip>
ssh ubuntu@<public-ip>

Retrieve the per-instance API password

Every instance generates its own 24-character API password on first boot and writes it to a root-only file. Read it once and store it somewhere safe:

sudo cat /root/presidio-credentials.txt

The file records the API URL, the username (admin) and the generated password. Nothing about the password is baked into the image — two instances launched from this AMI get two different passwords.

Confirm the services and the security posture

Both engines bind to loopback only; nginx on port 80 is the single reachable surface and requires authentication on every API path except the static /healthz probe.

systemctl is-active presidio-analyzer presidio-anonymizer nginx
ss -tln | grep -E '127.0.0.1:500[12]|:80 '
curl -s -o /dev/null -w 'healthz (no auth)  -> HTTP %{http_code}\n' http://127.0.0.1/healthz
curl -s -o /dev/null -w 'analyze (no auth)  -> HTTP %{http_code}\n' \
  -X POST http://127.0.0.1/analyze \
  -H 'Content-Type: application/json' -d '{"text":"hello","language":"en"}'

/healthz returns 200 without credentials; /analyze returns 401 without credentials.

Presidio security posture: both engines listen on 127.0.0.1 only, nginx serves port 80, the unauthenticated health probe returns HTTP 200 and an unauthenticated analyze request is refused with HTTP 401

Detect PII with the analyzer

POST free text to /analyze with the admin credential. Replace <your-password> with the password from the credentials file. The analyzer returns one object per detected entity with its type, start/end character offsets and a confidence score. All example data below uses the reserved example.com domain and the 555-01xx range reserved for fiction.

curl -s -u 'admin:<your-password>' \
  -X POST http://127.0.0.1/analyze \
  -H 'Content-Type: application/json' \
  -d '{"text":"My name is Jane Doe, email jane.doe@example.com, card 4095-2609-9393-4932, phone 212-555-0143.","language":"en"}'

List every entity type the analyzer can detect:

curl -s -u 'admin:<your-password>' http://127.0.0.1/supportedentities

Redact PII with the anonymizer

Feed the analyzer's results into /anonymize to remove the detected spans. This example replaces each span with its entity type; the anonymizer also supports mask, hash, redact and encrypt operators.

RESULTS=$(curl -s -u 'admin:<your-password>' \
  -X POST http://127.0.0.1/analyze \
  -H 'Content-Type: application/json' \
  -d '{"text":"My name is Jane Doe, email jane.doe@example.com.","language":"en"}')
curl -s -u 'admin:<your-password>' \
  -X POST http://127.0.0.1/anonymize \
  -H 'Content-Type: application/json' \
  -d "{\"text\":\"My name is Jane Doe, email jane.doe@example.com.\",\"analyzer_results\":$RESULTS,\"anonymizers\":{\"DEFAULT\":{\"type\":\"replace\",\"new_value\":\"[REDACTED]\"}}}"

The returned text has every detected span replaced, so the personal data no longer appears in the output.

Presidio redacting PII: a POST to /anonymize returns the text with the person name, email address, credit card number and phone number all replaced by their entity-type tags

Calling the API from another machine

From your own workstation, point any Presidio SDK client or HTTP tool at the instance's public IP over port 80 with the same credentials. The analyzer and anonymizer paths are served unchanged through the proxy:

curl -s -u 'admin:<your-password>' \
  -X POST http://<public-ip>/analyze \
  -H 'Content-Type: application/json' \
  -d '{"text":"Contact John Smith at john.smith@example.com","language":"en"}'

Only /healthz is reachable without the credential; every other path returns 401 until you authenticate.

Log hygiene for submitted text

Submitted text is the sensitive artifact for this product, so the image is configured not to persist it. Access logging is disabled on both services and on every nginx API path, and the services run at log level WARNING with Presidio's decision-process tracing left disabled. You can confirm nothing you submit is written to the journal:

curl -s -o /dev/null -u 'admin:<your-password>' \
  -X POST http://127.0.0.1/analyze \
  -H 'Content-Type: application/json' \
  -d '{"text":"audit probe for Alex Roe at alex.roe@example.com","language":"en"}'
sudo journalctl -u presidio-analyzer -u presidio-anonymizer -u nginx --no-pager --since "-5 min" | grep -c 'alex.roe@example.com' || true

The grep returns 0 — the submitted address does not appear in the logs.

The shipped language model and its licence

Presidio's own code is MIT, but it is inert without an NLP model, and models are separately licensed artifacts. This image ships the spaCy en_core_web_lg model, whose MIT licence is verified against the installed artifact at build time and recorded in a provenance file on the image:

cat /opt/presidio/MODEL-PROVENANCE.txt

Stanza and transformer-based recognizers are deliberately not installed, because many of those models carry share-alike or research-only licences that would not permit commercial redistribution.

Change the API password

To rotate the password, write a new nginx htpasswd entry and reload:

sudo sh -c 'printf "admin:%s\n" "$(openssl passwd -apr1 <new-password>)" > /etc/nginx/cloudimg-presidio.htpasswd'
sudo systemctl reload nginx

Enable HTTPS (recommended for production)

The image serves the API over plain HTTP on port 80. For production, terminate TLS in front of Presidio. Point a DNS record at the instance, open port 443, and install a certificate with certbot:

sudo apt-get update && sudo apt-get install -y certbot python3-certbot-nginx
sudo certbot --nginx -d <your-domain> --agree-tos -m admin@<your-domain> --redirect

certbot rewrites the nginx server block to serve the authenticated API over HTTPS and redirect HTTP to HTTPS.

Maintenance

  • Restart the stack: sudo systemctl restart presidio-analyzer presidio-anonymizer nginx
  • View service logs: sudo journalctl -u presidio-analyzer -n 50 --no-pager
  • Apply OS updates: sudo apt-get update && sudo apt-get upgrade -y

The analyzer loads its ~1 GiB language model at start, so allow a minute after a restart before the first request.

Support

This image is backed by 24/7 cloudimg technical support via email and live chat. We help with deployment, instance sizing for CPU NLP workloads, credential management, anonymization operators, model provenance and licensing questions, API integration, and TLS/VPC configuration for production. Critical issues receive a one-hour average response time. Contact support@cloudimg.co.uk.