S3Proxy on Ubuntu 24.04 on Azure User Guide
Overview
This guide covers the deployment and configuration of S3Proxy on Ubuntu 24.04 on Azure using cloudimg Azure Marketplace images. S3Proxy is an open source Java application that implements the Amazon S3 API and proxies requests to a storage backend. This image configures the local filesystem backend, so a plain virtual machine and its dedicated data disk become an S3 compatible object storage endpoint: point any S3 SDK, the AWS CLI, s3cmd, rclone or a backup tool at it and read and write buckets and objects exactly as you would against Amazon S3, entirely within your own subscription.
The image installs S3Proxy 3.3.0 from the official release, run under systemd as an unprivileged s3proxy user on the Java 17 runtime. The gateway binds to loopback only and is exposed through an nginx reverse proxy that terminates TLS on port 443, so the S3 API is reachable securely over HTTPS. It is a headless service: there is no web interface, so you operate it with any standard S3 client. The AWS CLI v2 ships in the image so you can test immediately on the VM.
Secure by default — SigV4 required, no default credentials. S3Proxy can be run with authorization disabled, which serves an open, anonymous S3 endpoint. This image closes that gap: it sets s3proxy.authorization=aws-v2-or-v4, so every request must be AWS Signature Version 2 or 4 signed. On the very first boot of every VM it generates a unique access key and secret key, writes them into the gateway configuration and records them in /root/s3proxy-info.txt (mode 0600, root only), and the service is configured to refuse to start until a key pair is present. As a result an unsigned or wrong key request is rejected, and no two instances share a credential.
What is included:
-
S3Proxy 3.3.0 from the official release, run under systemd as the unprivileged
s3proxyuser (s3proxy.service) on OpenJDK 17 -
An HTTPS S3 API endpoint on port 443, terminated by nginx with a self signed certificate generated per virtual machine on first boot
-
The local filesystem backend (Apache jclouds), storing buckets and objects on a dedicated data disk mounted at
/var/lib/s3proxy -
A unique access key and secret key generated on first boot and documented in
/root/s3proxy-info.txt(0600), withs3proxy.authorization=aws-v2-or-v4so every request is SigV4 authenticated -
AWS CLI v2 preinstalled so you can create buckets and read and write objects from the VM immediately
Prerequisites
-
Active Azure subscription, SSH public key, VNet + subnet in target region
-
Subscription to the S3Proxy listing on Azure Marketplace
-
Network Security Group rules allowing TCP 22 (admin) and TCP 443 (the HTTPS S3 API) from the client networks that will use the endpoint. Port 80 answers a redirect to HTTPS and an unauthenticated
/healthzload balancer probe
Recommended virtual machine size: Standard_B2s (2 vCPU, 4 GB RAM) is ample for development and small production workloads; the gateway runs with a 1024 MB JVM heap on this size. Scale to a larger size, or attach a larger data disk, for higher throughput or capacity.
Step 1: Deploy from the Azure Portal
Launch the image from the Azure Marketplace listing, choosing your resource group, region, size (Standard_B2s or larger) and SSH key. Attach the offer's data disk and open ports 22 and 443 in the Network Security Group. Equivalent Azure CLI:
RG="s3proxy-prod"; LOCATION="eastus"; VM_NAME="s3proxy1"
GALLERY_IMAGE_ID="/subscriptions/<sub-id>/resourceGroups/azure-cloudimg/providers/Microsoft.Compute/galleries/cloudimgGallery/images/s3proxy-ubuntu-24-04/versions/<version>"
SSH_KEY="$(cat ~/.ssh/id_rsa.pub)"
az group create --name "$RG" --location "$LOCATION"
az vm create \
--resource-group "$RG" --name "$VM_NAME" \
--image "$GALLERY_IMAGE_ID" \
--size Standard_B2s \
--admin-username azureuser \
--ssh-key-values "$SSH_KEY" \
--public-ip-sku Standard
az vm open-port --resource-group "$RG" --name "$VM_NAME" --port 443 --priority 1001
az vm open-port --resource-group "$RG" --name "$VM_NAME" --port 22 --priority 1002
Step 2: Retrieve your per-VM S3 credentials
On first boot the VM generates a unique access key and secret key and writes them to a root only file. SSH in as azureuser and read them:
sudo cat /root/s3proxy-info.txt
You will see S3PROXY_ACCESS_KEY, S3PROXY_SECRET_KEY and S3PROXY_ENDPOINT_URL (the HTTPS endpoint using this VM's public IP). These keys are unique to this VM and were not baked into the image. Keep this file secret.
Step 3: Verify the service is running
Confirm the gateway, the Java runtime and the network listeners. S3Proxy binds to loopback (127.0.0.1:8080); nginx binds the public HTTPS endpoint on 0.0.0.0:443:
systemctl is-active s3proxy.service nginx.service
java -version
ss -tlnH | grep -E ':8080|:443'

Step 4: Create a bucket and store objects
The AWS CLI ships in the image. Because the endpoint is an IP address with a self signed certificate, use --endpoint-url and --no-verify-ssl. The following block reads this VM's own keys and runs a full round trip against the local endpoint: create a bucket, upload an object, list it, then download it and print its contents.
export AWS_ACCESS_KEY_ID=$(sudo grep '^S3PROXY_ACCESS_KEY=' /root/s3proxy-info.txt | cut -d= -f2-)
export AWS_SECRET_ACCESS_KEY=$(sudo grep '^S3PROXY_SECRET_KEY=' /root/s3proxy-info.txt | cut -d= -f2-)
export AWS_DEFAULT_REGION=us-east-1
echo "hello from cloudimg" > /tmp/demo.txt
aws --no-verify-ssl --endpoint-url https://127.0.0.1/ s3 mb s3://demo-bucket
aws --no-verify-ssl --endpoint-url https://127.0.0.1/ s3 cp /tmp/demo.txt s3://demo-bucket/demo.txt
aws --no-verify-ssl --endpoint-url https://127.0.0.1/ s3 ls s3://demo-bucket/
aws --no-verify-ssl --endpoint-url https://127.0.0.1/ s3 cp s3://demo-bucket/demo.txt -
aws --no-verify-ssl --endpoint-url https://127.0.0.1/ s3 rb s3://demo-bucket --force

Step 5: Authentication is enforced
There is no anonymous access. An unsigned request is rejected with HTTP 403, and a request signed with the wrong secret key is rejected with SignatureDoesNotMatch:
curl -ks -o /dev/null -w 'unsigned request: HTTP %{http_code}\n' https://127.0.0.1/
AK=$(sudo grep '^S3PROXY_ACCESS_KEY=' /root/s3proxy-info.txt | cut -d= -f2-)
AWS_ACCESS_KEY_ID=$AK AWS_SECRET_ACCESS_KEY=wrong-secret AWS_DEFAULT_REGION=us-east-1 \
aws --no-verify-ssl --endpoint-url https://127.0.0.1/ s3 ls >/dev/null 2>&1 \
&& echo 'UNEXPECTED: wrong key accepted' \
|| echo 'wrong key rejected (expected, SignatureDoesNotMatch)'

Step 6: Security posture and where your data lives
The credentials file is root only (0600), s3proxy.authorization is aws-v2-or-v4, and bucket and object data lives on the dedicated data disk mounted at /var/lib/s3proxy:
sudo stat -c '%n %a %U:%G' /root/s3proxy-info.txt
sudo grep '^s3proxy.authorization=' /etc/s3proxy/s3proxy.conf
df -h /var/lib/s3proxy | tail -1

Connecting from a remote client
From your own machine, configure any S3 client with this VM's keys and its public endpoint. With the AWS CLI, configure a named profile and force path style addressing (the endpoint is an IP address):
aws configure set aws_access_key_id <your-access-key> --profile s3proxy
aws configure set aws_secret_access_key <your-secret-key> --profile s3proxy
aws configure set region us-east-1 --profile s3proxy
aws configure set s3.addressing_style path --profile s3proxy
aws --profile s3proxy --endpoint-url https://<vm-ip>/ --no-verify-ssl s3 ls
rclone works the same way, with an S3 remote whose provider = Other, endpoint = https://<vm-ip>/, access_key_id and secret_access_key set to your keys, and force_path_style = true. Any tool or SDK that speaks the S3 API (restic, Veeam, s3cmd, boto3) can target the endpoint unchanged.
Using the TLS certificate
The certificate is self signed and regenerated per virtual machine on first boot, with the VM public IP, hostname and 127.0.0.1 in its subject alternative names. For quick testing pass --no-verify-ssl (AWS CLI) or the equivalent. For production, either distribute the certificate at /etc/nginx/tls/s3proxy.crt to your clients as a trusted CA, or place the appliance behind a load balancer or reverse proxy with a certificate issued by a public CA for your own domain, and point clients at that hostname.
Rotating the access keys
The key pair lives in two lines of the gateway configuration, s3proxy.identity (the access key) and s3proxy.credential (the secret key). To rotate it, set new values, keep /root/s3proxy-info.txt in step, and restart the service. This example generates a fresh random key pair and applies it:
NEW_ACCESS_KEY=$(openssl rand -hex 10 | tr 'a-z' 'A-Z')
NEW_SECRET_KEY=$(openssl rand -base64 48 | tr -dc 'A-Za-z0-9' | head -c 40)
sudo sed -i "s|^s3proxy.identity=.*|s3proxy.identity=$NEW_ACCESS_KEY|" /etc/s3proxy/s3proxy.conf
sudo sed -i "s|^s3proxy.credential=.*|s3proxy.credential=$NEW_SECRET_KEY|" /etc/s3proxy/s3proxy.conf
sudo sed -i "s|^S3PROXY_ACCESS_KEY=.*|S3PROXY_ACCESS_KEY=$NEW_ACCESS_KEY|" /root/s3proxy-info.txt
sudo sed -i "s|^S3PROXY_SECRET_KEY=.*|S3PROXY_SECRET_KEY=$NEW_SECRET_KEY|" /root/s3proxy-info.txt
sudo systemctl restart s3proxy.service
echo "key pair rotated and recorded in /root/s3proxy-info.txt"
Update any clients with the new keys. To set your own values instead, edit /etc/s3proxy/s3proxy.conf directly and restart the service.
Managing the service
systemctl status s3proxy.service nginx.service --no-pager
sudo journalctl -u s3proxy.service -n 50 --no-pager
The gateway restarts automatically on failure and starts on boot. Object data persists on the data disk across restarts and reboots.
Troubleshooting
-
A client reports SignatureDoesNotMatch: confirm the client is using the exact access key and secret key from
/root/s3proxy-info.txtand path style addressing. -
A client reports a certificate error: the certificate is self signed. Trust
/etc/nginx/tls/s3proxy.crt, use--no-verify-sslfor testing, or front the appliance with a real certificate as described above. -
A client cannot connect: confirm the Network Security Group allows TCP 443 from the client network, and that
systemctl is-active s3proxy.service nginx.servicereportsactive.
Support
Every cloudimg deployment includes 24/7 support. Contact the cloudimg team for help with configuration, backends, scaling or integration.