ImgProxy is an open-source image-processing server — resize, convert, and transform images on the fly via URL parameters, ideal as a caching layer in front of a CDN or web app. This guide builds ImgProxy from source on Ubuntu, runs it as a systemd service behind Nginx with TLS, walks through its URL processing options, and secures it with signed URLs.

Prerequisites: an Ubuntu server, a domain A record (e.g. imgproxy.example.com), non-root sudo user.


Install ImgProxy

ImgProxy uses libvips for image processing; this builds it from source with Go.

$ sudo add-apt-repository ppa:dhor/myway
$ sudo apt update
$ sudo apt install libvips-dev -y
$ sudo snap install --classic --channel=latest/stable go
$ git clone https://github.com/imgproxy/imgproxy.git
$ cd imgproxy
$ sudo CGO_LDFLAGS_ALLOW="-s|-w"   go build -o /usr/local/bin/imgproxy

Enter fullscreen mode Exit fullscreen mode

Create the environment config:

$ sudo touch /usr/local/bin/imgproxy.env
$ sudo nano /usr/local/bin/imgproxy.env

Enter fullscreen mode Exit fullscreen mode

IMGPROXY_BIND=:8080
IMGPROXY_NETWORK=tcp

IMGPROXY_READ_TIMEOUT=10
IMGPROXY_WRITE_TIMEOUT=10

IMGPROXY_WORKERS=2
IMGPROXY_REQUESTS_QUEUE_SIZE=0

IMGPROXY_QUALITY=100
IMGPROXY_PREFERRED_FORMATS=webp,jpeg,png,gif,avif

IMGPROXY_LOG_FORMAT="pretty"
IMGPROXY_LOG_LEVEL="INFO"

IMGPROXY_WATERMARK_URL=https://example.com/watermark.png
IMGPROXY_WATERMARK_OPACITY=1

Enter fullscreen mode Exit fullscreen mode

Key settings: IMGPROXY_WORKERS should be ~2× your vCPU count; IMGPROXY_REQUESTS_QUEUE_SIZE=0 means unlimited queueing; IMGPROXY_WATERMARK_URL points at whatever image you want overlaid when watermarking is enabled.

Point ImgProxy at the config and test:

$ export IMGPROXY_ENV_LOCAL_FILE_PATH=/usr/local/bin/imgproxy.env
$ cd
$ imgproxy

Enter fullscreen mode Exit fullscreen mode

WARNING [2024-05-28T00:40:42Z] No keys defined, so signature checking is disabled
WARNING [2024-05-28T00:40:42Z] No salts defined, so signature checking is disabled
INFO    [2024-05-28T00:40:42Z] Starting server at :8080

Enter fullscreen mode Exit fullscreen mode

Stop it with Ctrl+C once verified, then set it up as a service.


Run ImgProxy as a systemd Service

$ sudo useradd -r -s /bin/false imgproxy
$ sudo chown imgproxy:imgproxy /usr/local/bin/imgproxy
$ sudo chown imgproxy:imgproxy /usr/local/bin/imgproxy.env
$ sudo nano /etc/systemd/system/imgproxy.service

Enter fullscreen mode Exit fullscreen mode

[Unit]
Description=IMGProxy Image Processing Application
After=network.target

[Service]
Environment="IMGPROXY_ENV_LOCAL_FILE_PATH=/usr/local/bin/imgproxy.env"
ExecStart=/usr/local/bin/imgproxy
User=imgproxy
Group=imgproxy
Restart=always
StandardOutput=append:/var/log/imgproxy/access.log

[Install]
WantedBy=multi-user.target

Enter fullscreen mode Exit fullscreen mode

$ sudo mkdir -p /var/log/imgproxy/
$ sudo chown -R imgproxy:imgproxy /var/log/imgproxy
$ sudo systemctl daemon-reload
$ sudo systemctl enable imgproxy
$ sudo systemctl start imgproxy
$ sudo systemctl status imgproxy

Enter fullscreen mode Exit fullscreen mode


Put Nginx in Front

$ sudo apt install nginx -y
$ sudo nano /etc/nginx/sites-available/imgproxy.example.com

Enter fullscreen mode Exit fullscreen mode

server {
    listen 80;
    server_name imgproxy.example.com;

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_redirect off;
    }
}

Enter fullscreen mode Exit fullscreen mode

$ sudo ln -s /etc/nginx/sites-available/imgproxy.example.com /etc/nginx/sites-enabled/
$ sudo nginx -t
$ sudo systemctl restart nginx
$ sudo ufw allow 80 comment 'ImgProxy Http'
$ sudo ufw reload

Enter fullscreen mode Exit fullscreen mode

TLS:

$ sudo apt install python3-certbot-nginx -y
$ sudo certbot --nginx -d imgproxy.example.com --agree-tos
$ sudo systemctl restart nginx
$ sudo ufw allow 443 comment 'ImgProxy Https'
$ sudo ufw reload

Enter fullscreen mode Exit fullscreen mode


Process Images via URL

ImgProxy's URL format is https://imgproxy-url/[processing-options]/source-image-url, supporting plain, encoded, or encrypted source URLs.

1. Confirm the default page loads:

https://imgproxy.example.com

Enter fullscreen mode Exit fullscreen mode

2. Load a source image unprocessed (insecure/plain mode — fine for testing, not for production):

https://imgproxy.example.com/insecure/plain/https://images.pexels.com/photos/72594/japan-train-railroad-railway-72594.jpeg

Enter fullscreen mode Exit fullscreen mode

3. Resize it with the rs:fill:800:800 option (keeps aspect ratio while filling the target dimensions):

https://imgproxy.example.com/insecure/rs:fill:800:800/plain/https://images.pexels.com/photos/72594/japan-train-railroad-railway-72594.jpeg

Enter fullscreen mode Exit fullscreen mode

4. Convert format with @webp (check the Content-Type response header in devtools → Network to confirm):

https://imgproxy.example.com/insecure/rs:fill:800:800/plain/https://images.pexels.com/photos/72594/japan-train-railroad-railway-72594.jpeg@webp

Enter fullscreen mode Exit fullscreen mode

5. Add a watermark with watermark:1:ce:2:2 (enabled, centered, 2/2 x/y offset):

https://imgproxy.example.com/insecure/rs:fill:800:800/watermark:1:ce:2:2/plain/https://images.pexels.com/photos/72594/japan-train-railroad-railway-72594.jpeg

Enter fullscreen mode Exit fullscreen mode

See the full processing options reference for everything else supported.


Protect ImgProxy with Signed URLs

Without a key/salt pair, anyone can hit ImgProxy with arbitrary options. Signing locks requests to specific, pre-approved transformations.

1. Generate a key and salt:

$ openssl rand -hex 32
$ openssl rand -hex 16

Enter fullscreen mode Exit fullscreen mode

2. Add them to the config:

$ sudo nano /usr/local/bin/imgproxy.env

Enter fullscreen mode Exit fullscreen mode

IMGPROXY_KEY=your-generated-key
IMGPROXY_SALT=your-generated-salt

Enter fullscreen mode Exit fullscreen mode

3. Script a signature generator:

$ nano imgproxy-signing.sh

Enter fullscreen mode Exit fullscreen mode

#!/bin/bash

KEY_VALUE="your-generated-key"
SALT_VALUE="your-generated-salt"

KEY=$(echo -n "$KEY_VALUE" | xxd -r -p)
SALT=$(echo -n "$SALT_VALUE" | xxd -r -p)

URL_PATH="/rs:fill:800:800/watermark:1:ce:2:2/plain/https://images.pexels.com/photos/72594/japan-train-railroad-railway-72594.jpeg"
IMGPROXY_URL="https://imgproxy.example.com/"

DATA="$SALT$URL_PATH"

SIGNATURE=$(echo -n "$DATA" | openssl dgst -sha256 -hmac "$KEY" -binary | base64 -w 0 | tr '+/' '-_' | tr -d '=')

echo "The Signature based on your key/salt pair is: $SIGNATURE \n"
echo "The Signed ImgProxy URL is: $IMGPROXY_URL$SIGNATURE$URL_PATH"

Enter fullscreen mode Exit fullscreen mode

$ bash imgproxy-signing.sh
$ sudo systemctl restart imgproxy

Enter fullscreen mode Exit fullscreen mode

Visit the generated signed URL to confirm it works. Reusing that signature with different processing options (e.g. adding blur:10) gets rejected with a 403 Invalid signature — one signature is bound to one exact transformation.

Trust a signature for reuse across multiple source URLs:

$ sudo nano /usr/local/bin/imgproxy.env

Enter fullscreen mode Exit fullscreen mode

IMGPROXY_TRUSTED_SIGNATURES=your-signature-here

Enter fullscreen mode Exit fullscreen mode

Comma-separate multiple trusted signatures. Restart ImgProxy and confirm the trusted signature now works against different source images/formats — useful when a dynamic app (WordPress, etc.) needs to serve many distinct images without regenerating a signature per URL.

$ sudo systemctl restart imgproxy

Enter fullscreen mode Exit fullscreen mode


Process Images from S3-Compatible Object Storage

ImgProxy can pull directly from S3-compatible buckets instead of arbitrary URLs.

$ sudo nano /usr/local/bin/imgproxy.env

Enter fullscreen mode Exit fullscreen mode

IMGPROXY_USE_S3=true
IMGPROXY_S3_ENDPOINT=your-region.your-provider.com
AWS_ACCESS_KEY_ID=your-access-key
AWS_SECRET_ACCESS_KEY=your-secret-key

Enter fullscreen mode Exit fullscreen mode

Reference bucket objects with the s3://bucket_name/file-name URI in place of an HTTP source URL:

https://imgproxy-url/processing-options/s3://bucket-name/file-name

Enter fullscreen mode Exit fullscreen mode


Next Steps

ImgProxy is running behind Nginx with TLS, URL signing, and object-storage support. From here:

  • Set IMGPROXY_TTL and CDN caching headers to avoid reprocessing the same transform repeatedly
  • Add Prometheus + Grafana to track processing latency and request volume
  • Move IMGPROXY_KEY/IMGPROXY_SALT into a secrets manager instead of the plain env file for production

For the full guide, visit the original article on Vultr Docs.