What you're actually doing
Migration is not a single task. It is a sequence of tasks, each with dependencies, each with failure modes, some of which only reveal themselves after you've started.
This guide covers the methodology — how to assess, plan, and execute a migration from public cloud or Microsoft 365 to private infrastructure — without destroying your operations in the process.
Phase 1: Assessment (do not skip this)
Before moving anything, understand what you have.
Workload inventory For every service you're running, document:
- What it does
- What it depends on (databases, other services, external APIs)
- How much data it has
- How much traffic it receives
- What the impact of 4 hours of downtime would be
This inventory takes longer than you expect. Budget 2–4 weeks for a medium-sized environment.
The repatriation decision framework Not everything should move. Apply this filter to each workload:
Move to private infrastructure if:
- It runs 24/7 with predictable resource requirements
- It handles sensitive data that shouldn't be on US-owned infrastructure
- Its cloud costs are high relative to the compute it actually uses
- It has no elastic demand — it doesn't need to scale from zero to 100 overnight
Keep in public cloud if:
- It has genuinely unpredictable, spiky demand
- It uses managed services with no good open source equivalent (some ML pipelines, some analytics)
- The migration cost exceeds 18 months of cloud savings
Data volumes Download a cost and usage report from AWS or Azure. Find every S3 bucket, EBS volume, RDS instance, and service. Map actual usage vs. provisioned capacity. This is where you find the waste.
Phase 2: Target environment
Stand up your private infrastructure before you start migrating.
For most migrations:
- Proxmox cluster (or single node for small environments)
- OPNsense for networking
- Proxmox Backup Server
- TOWER monitoring configured and alerting
- SSO (Authentik/Keycloak) if replacing Azure AD
Do not migrate into an environment that isn't stable and monitored. You'll be debugging the infrastructure and the migration simultaneously.
AWS/Azure workload migration
Databases Migrate databases first — they're the riskiest and need the most validation time.
PostgreSQL:
# On AWS RDS - dump
pg_dump -h your-rds-endpoint -U username dbname > db_backup.sql
# On new server - restore
psql -h new-server -U username dbname < db_backup.sql
Run both databases in parallel. Point a test version of your application at the new database. Validate data integrity before switching production traffic.
MySQL/MariaDB — same approach with mysqldump.
Storage (S3) Use rclone for S3 bucket transfer:
# Configure rclone with AWS source and MinIO destination
rclone sync s3:your-bucket minio:your-bucket --progress
For large buckets: run the initial sync, then run it again just before cutover to catch changes. Don't try to do a single sync of a large bucket and cut over immediately — things change.
Application VMs For Linux VMs: snapshot → export → import to Proxmox.
AWS: create an AMI, export to S3 as VMDK, convert with qemu-img, import to Proxmox.
Or, better for most cases: provision a fresh VM on Proxmox, redeploy from your configuration management (Ansible/Terraform), migrate data separately. Avoid carrying forward accumulated VM cruft.
DNS cutover Reduce TTL to 60 seconds 48 hours before cutover. Make the change. Wait 15 minutes. If something is wrong, you can revert quickly while old DNS is still cached elsewhere.
After successful cutover: don't immediately terminate AWS instances. Keep them running for 48–72 hours as fallback. Then terminate.
Microsoft 365 exit
M365 exit is the most common migration we see. The mapping:
| M365 | Private replacement | Tool |
|---|---|---|
| Exchange/Outlook | iRedMail + Proofpoint + CrossBox | imapsync |
| SharePoint/OneDrive | Nextcloud | rclone |
| Teams | Mattermost or Matrix/Element | manual |
| Azure AD/Entra | Keycloak or Authentik | manual |
| OneDrive sync | Nextcloud desktop client | — |
Mail migration with imapsync
imapsync --host1 outlook.office365.com --ssl1 --user1 user@company.com --password1 "M365pass" --host2 your-mail-server --ssl2 --user2 user@company.com --password2 "newpass"
Run imapsync first in dry-run mode (--dry). Then run for real. Then run again at cutover time to catch mail received during migration.
File migration from SharePoint/OneDrive
rclone sync onedrive:Documents nextcloud:Documents --progress
Rclone supports OneDrive natively. Configure once, run the sync, then run again at cutover. Preserve folder structure — Nextcloud maps directly.
The hard part: Teams There is no good automated migration from Teams to Mattermost or Matrix. Message history can be exported from Teams (compliance export) but importing into Mattermost requires custom scripting.
Practical approach: don't migrate Teams history. Set a cutover date. Archive Teams export for compliance. Start fresh in Mattermost. Most teams accept this — old message history is rarely accessed after 90 days.
Azure AD No good automated migration. The approach: recreate users in Keycloak or Authentik, set temporary passwords, force password reset on first login.
If you're running hybrid AD (on-prem + Azure AD): run Keycloak with LDAP sync from your on-prem AD as a transition step.
What takes longer than you expect
DNS propagation — you know this, and you still underestimate it. 48-hour TTL reduction is not optional. Budget an extra day.
Mail deliverability warmup — a new IP sending full mail volume immediately looks like a spam operation. Warm up gradually: 10% of volume on day 1, 25% on day 3, 50% on day 7, 100% on day 14. This matters.
User acceptance — the new interface is not Outlook. Budget time for user questions, training, and the inevitable "can we go back" conversations. They pass within 2 weeks.
Edge cases in SharePoint — SharePoint custom column types, metadata, and permissions don't map cleanly to Nextcloud folder structure. Identify these before migration, not during.
Leavers and inactive accounts — every M365 tenant has accounts for people who left 3 years ago, still with mail in their mailboxes. Decide your policy for these before starting.
Honest timeline estimates
| Migration scope | Realistic timeline |
|---|---|
| Mail only (< 50 users) | 2–4 weeks |
| Full M365 exit (< 50 users) | 6–10 weeks |
| Full M365 exit (50–200 users) | 3–6 months |
| AWS workload migration (< 20 VMs) | 4–8 weeks |
| AWS workload migration (20+ VMs) | 3–6 months |
These assume part-time effort (not a dedicated migration team). Double the time estimate if this is being done alongside normal operations by the same people responsible for production.
Or let PILOT run it
Migration is complex. We've done it before. Assessment, planning, execution, validation — that's the migration service.