Free & Open Source (MIT)PHP 8.1+ · MySQLZero Runtime Deps191 Passing Tests (PHPUnit 11)

The S3 storage server that runs on shared hosting.

Amazon S3 API — SigV4, multipart, presigned URLs, aws-chunked streaming — as a plain PHP app you deploy like WordPress: upload, open the installer, point your AWS SDK at it.

No Go binary. No Docker. No root. No daemon. No Composer dependencies at runtime. Just PHP 8.1+ and MySQL/MariaDB on any Apache, LiteSpeed or cPanel shared host.

Why not just use MinIO (or anything else)?

Because MinIO needs a server you control — a Go binary, a long-running process, a port to bind — and your $5/month cPanel plan can't give you any of that. And because the other PHP S3 servers each fail a different way:

You want…The catch elsewhere php-s3
S3 on shared hosting (cPanel, LiteSpeed, no root)MinIO/others need a server you administer✅ Plain public/index.php + .htaccess, install via browser
Signatures that are actually verifiedlite-s3 parses SigV4 but the wired path never checks it (see research)✅ Strict SigV4 — header + presigned + per-chunk chain, hash_equals, no bypass flags
Uploads of big files in constant memorysimple-php-s3-server buffers whole PUT bodies in RAM✅ Everything is a 64 KiB streaming loop, hash-while-writing
Listings that don’t degradebuckie/simple-php re-scan the directory per list page — O(n)✅ DB is the index; sharded object layout, pagination stays fast
Real S3 semantics (XML API, AWS error codes, composite ETags)buckie-php isn’t S3 at all; lite-s3’s ETags aren’t AWS-format✅ Spec-shaped: md5(...)-N multipart ETags, AWS error taxonomy
A project you can read and auditheavyweight options hide behind frameworks✅ Framework-free layered core, research + architecture docs in-repo

And unlike opsfour/s3-server — the best-in-class PHP S3 server, which we openly credit as our quality bar — php-s3 doesn't require PHP 8.4, Amp fibers, and long-running workers. Same correctness bar, deployable where shared hosting exists.

How php-s3 compares.

Fair summary of the four open-source PHP projects we studied in full source before writing a line of code (audit: docs/research/):

Capabilityphp-s3opsfour/s3-serversimple-php-s3-serverlite-s3buckie-php
Real S3 XML API + SigV4✅✅✅⚠️❌
Signature verified on every request✅✅⚠️❌n/a
Runs on shared hosting (PHP 8.1, no daemon)✅❌✅✅✅
Streaming PUT/GET (constant memory)✅✅❌⚠️✅
DB-indexed listings (no full scans)✅✅❌✅❌
Multipart with AWS-format ETag✅✅✅⚠️❌
Presigned URLs (GET/PUT, expiry)✅✅⚠️⚠️❌
aws-chunked + per-chunk signatures✅✅❌❌❌
Automated tests in the repo✅ 191✅ 745⚠️❌✅ 56
Web installer + admin panel✅❌❌✅❌
Runtime dependencies0Amp + Symfony2 packages00

✅ supported & proven · ⚠️ partial or broken · ❌ not supported

Features.

S3-Compatible API

Buckets, objects, ListObjectsV1/V2 (prefix, delimiter, continuation tokens), DeleteObjects batch, Range/206 for video seeking, and CopyObject.

AWS Signature Version 4

Header mode and presigned URLs (1–604800 s expiry), ±900 s skew, host required in SignedHeaders, constant-time comparison, payload hash verified against the streamed body.

aws-chunked Streaming Uploads

Signed chunk chains and unsigned-trailer framing, verified per chunk; tampered chunks rejected immediately with SignatureDoesNotMatch.

Multipart Uploads

Create, uploadPart, complete, abort, listUploads, listParts, AWS-format composite ETags, and 5 MiB minimum-part enforcement (EntityTooSmall).

Streaming Everything

Bodies are pumped in 64 KiB chunks while MD5 and SHA-256 are computed on the fly; nothing ever loads a whole object into memory.

Safe Storage Layout

sha256(key)-sharded paths + UUID names: object keys never touch the filesystem path (traversal structurally impossible), data root lives outside the web root.

Shared-Hosting Plumbing

public/ docroot, .htaccess with Authorization header passthrough SigV4 needs on Apache/LiteSpeed, dotfiles blocked, and self-locking web installer.

Minimal Admin

Session login with throttling (10 attempts/hr/IP), access-key management with bucket allow-lists, bucket overview, usage stats, and audit log.

Maintenance CLI

CLI commands for maintenance: migrate (schema migrations), gc (purge expired multipart uploads and orphan temp files), key:create, and doctor.

Honest AWS Errors

AWS error taxonomy with correct HTTP statuses and RequestId, so boto3, rclone, and official AWS SDKs fail gracefully instead of mysteriously.

Zero Runtime Dependencies

PHP ≥ 8.1 with pdo_mysql, openssl, fileinfo, and mbstring. No Composer dependencies in production; PHPUnit and AWS SDK are dev-only.

S3 API coverage.

A feature counts as supported only when a test proves it through a real client:

AreaOperations
ServiceListBuckets, GET /_health
BucketsCreateBucket, DeleteBucket (empty only), HeadBucket
ObjectsPutObject, GetObject (+ Range/206), HeadObject, DeleteObject, CopyObject*
ListingListObjectsV1, ListObjectsV2 (prefix, delimiter/CommonPrefixes, markers, tokens)
BatchDeleteObjects (≤1000 keys, Quiet mode)
MultipartCreateMultipartUpload, UploadPart, CompleteMultipartUpload, AbortMultipartUpload, ListMultipartUploads, ListParts
AuthSigV4 header, presigned query URLs, aws-chunked streaming
Nextchecksum headers, conditional requests, boto3 suite, rclone docs

* routed and implemented; end-to-end proof pending. Full matrix with evidence in docs/s3-compatibility/.

Requirements.

PHP Version8.1+

pdo_mysql, openssl, fileinfo, mbstring

DatabaseMySQL 5.7+ / MariaDB 10.3+

Any DB PDO can reach

Web ServerApache, LiteSpeed, nginx, Caddy

.htaccess included

Runtime DepsZero

No Composer required on server

Quick start.

Option A

Shared Hosting (cPanel / LiteSpeed / Any Host)

git clone https://github.com/Luckyyaduvanshiofficial/php-s3.git
cd php-s3
composer install --no-dev  # or copy vendor/ from your dev machine
  1. Point your domain (or subdomain) document root at public/
  2. Visit https://your-domain.example/_admin/install and follow the wizard (writes config.php outside docroot and runs migrations)
  3. Log in, create an access key, create a bucket
  4. Point any S3 client at your domain — path-style, custom endpoint
Option B

Local Development

git clone https://github.com/Luckyyaduvanshiofficial/php-s3.git
cd php-s3
composer install
php -S 127.0.0.1:8099 -t public public/index.php
# open http://127.0.0.1:8099/_admin/install

Run the automated suite to verify compatibility on your local machine:

composer test  # 191 tests, 334 assertions (PHPUnit 11)

Connect your S3 clients.

Configure any standard S3 client in path-style mode with your custom endpoint:

Official AWS SDK for PHP
aws/aws-sdk-php
use Aws\S3\S3Client;

$s3 = new S3Client([
  'version' => 'latest',
  'region'  => 'us-east-1',
  'endpoint' => 'https://your-domain.example',
  'use_path_style_endpoint' => true, // required (path-style)
  'credentials' => [
    'key'    => 'AKIA...',
    'secret' => '...',
  ],
]);

$s3->createBucket(['Bucket' => 'media']);
$s3->putObject([
  'Bucket' => 'media',
  'Key'    => 'video.mp4',
  'SourceFile' => __DIR__ . '/video.mp4',
]);

// Multipart, ranged downloads and presigned URLs work unchanged:
$cmd = $s3->getCommand('GetObject', ['Bucket' => 'media', 'Key' => 'video.mp4']);
$url = (string) $s3->createPresignedRequest($cmd, '+1 hour')->getUri();
Python (boto3)
boto3
import boto3
from botocore.config import Config

s3 = boto3.client(
  "s3",
  endpoint_url="https://your-domain.example",
  aws_access_key_id="AKIA...",
  aws_secret_access_key="...",
  region_name="us-east-1",
  config=Config(s3={"addressing_style": "path"}), # required
)

s3.create_bucket(Bucket="media")
s3.upload_file("video.mp4", "media", "video.mp4")

# Presigned download link
url = s3.generate_presigned_url(
  "get_object",
  Params={"Bucket": "media", "Key": "video.mp4"},
  ExpiresIn=3600,
)
AWS CLI
aws s3
export AWS_ACCESS_KEY_ID="AKIA..."
export AWS_SECRET_ACCESS_KEY="..."
export AWS_DEFAULT_REGION="us-east-1"
ENDPOINT="https://your-domain.example"

# List buckets and objects
aws s3 ls --endpoint-url $ENDPOINT
aws s3 ls s3://media/ --endpoint-url $ENDPOINT

# Upload and sync directories
aws s3 cp ./video.mp4 s3://media/ --endpoint-url $ENDPOINT
aws s3 sync ./assets/ s3://media/assets/ --endpoint-url $ENDPOINT
rclone
rclone.conf
# ~/.config/rclone/rclone.conf
[php-s3]
type = s3
provider = AWS
access_key_id = AKIA...
secret_access_key = ...
endpoint = https://your-domain.example
region = us-east-1

# Commands
rclone lsd php-s3:
rclone copy ./photos php-s3:media/photos
rclone sync /var/www/uploads php-s3:backups/uploads

Architecture.

Clean layered design engineered from the ground up for strict correctness and shared hosting safety:

                       ┌────────────────────────────────────────────┐
   S3 client ──HTTP──► │ public/index.php  (only web-reachable file)│
  (SDK, rclone, …)     └──────────────┬─────────────────────────────┘
                                      ▼
        ┌──────────────┬──────────────┼──────────────┬───────────────┐
        ▼              ▼              ▼              ▼               ▼
   Http/Request   Auth/SigV4     S3/Operation    Admin panel     XML/Error
   (only layer      (header +     resolver ──►  (session auth,   (AWS taxonomy,
    touching        presigned +    dispatches    keys, usage)     XXE-safe)
    $_SERVER)       aws-chunked)   to handlers
                                      ▼
                          S3/Handlers ──► Storage/ (streaming put/get,
                                          sharded FS + MySQL index)

One Stack, One Entrypoint

No framework, no middleware maze, one single predictable request pipeline from incoming HTTP to response.

Transport-Agnostic Core

Nothing outside Http/ reads $_SERVER; auth, XML, routing, and errors are pure functions over strings and arrays.

Staging + Atomic Rename

Objects are staged outside the active namespace and become visible only when fully written and verified.

Security.

  • Strict SigV4: No bypass flags and no "simple auth" mode — every S3 request is cryptographically signed.
  • Anti-enumeration: Permission checked before existence (403 ≠ 404), preventing probing for bucket or object existence.
  • Constant-time comparisons:hash_equals used for every signature, secret, and ETag comparison.
  • Defensive grammar: Bucket-name grammar + segment-based key canonicalization + ^[a-f0-9]{32}$ uploadId whitelist. Traversal defense by validation, not sanitizing.
  • XXE-safe XML:DOCTYPE and ENTITY rejected before parse, LIBXML_NONET enabled, with an 8 MiB size cap.
  • Isolated storage: Objects stored outside the web root; engine-off rules for data dirs; dotfiles strictly denied.
  • Admin protection: Login throttling + security audit log in admin panel; secrets displayed only once, at creation.

CLI Commands

Headless management scripts for cron jobs, CI pipelines, and terminal administration:

# Apply schema migrations (idempotent)
php cli/php-s3.php migrate
# Environment & config health check
php cli/php-s3.php doctor
# Purge expired multipart uploads & temp files
php cli/php-s3.php gc
# Create access keys from command line
php cli/php-s3.php key:create --owner=1 --buckets='*' --description=ci

Project status & roadmap.

Open-source development progress tracked phase by phase:

PhaseScope Status
1 · ResearchFull source audit of 4 reference projects✅ docs/RESEARCH.md
2 · ArchitectureDesign decisions recorded before code✅ docs/ARCHITECTURE.md
3 · MVPSigV4, buckets, objects, ranges, listing, installer, CLI✅ shipped
4 · S3 compatibilityPresigned, DeleteObjects, multipart, aws-chunked✅ matrix
5 · HardeningS3 rate limits, quotas, CORS, public-read policy, recovery🚧 in progress
6 · DocumentationDeployment guides, boto3/rclone suites, SDK matrix CI⏳ next

Contributions welcome — especially: boto3 compatibility suite, rclone/AWS CLI smoke docs, x-amz-checksum-*, conditional requests, CI workflow.

Frequently asked questions.

Why run an S3 server in PHP instead of MinIO or Rust/Go?

MinIO and Go/Rust servers require a dedicated server, root access, background systemd daemons, and custom open ports. Shared hosting gives you none of that. Most developers already have cPanel or LiteSpeed hosting with unused gigabytes. php-s3 turns that existing hosting into an S3-compatible object store for ₹0 extra.

Does php-s3 strictly validate AWS SigV4 signatures?

Yes. Unlike earlier hobbyist PHP scripts that bypassed signature verification or only parsed headers, php-s3 enforces strict AWS SigV4 on every request. It verifies Authorization header mode and presigned URLs (1 s–7 days expiry, ±900 s clock skew), uses constant-time hash_equals comparison, and validates SHA-256 payload hashes against the streamed body.

Can it upload and download multi-gigabyte files without running out of memory?

Yes. Every body is a 64 KiB streaming pump with hash-while-writing. Requests and responses are never buffered entirely in RAM. It comfortably handles large file uploads within shared hosting limits (even standard 128 MB memory_limit environments).

Does it support multipart uploads?

Yes. It implements the full S3 multipart upload lifecycle: CreateMultipartUpload, UploadPart, CompleteMultipartUpload, AbortMultipartUpload, ListMultipartUploads, and ListParts with AWS-compliant composite ETags (e.g. md5-N) and enforcement of the 5 MiB minimum part size.

Where is data stored and is it protected from directory traversal?

The data directory is configured outside the web document root, making direct HTTP access structurally impossible. Object keys on disk are SHA-256 sharded (/data_root/objects/bu/ck/bucket_id/sha256(key)), preventing any path traversal exploits.

What are the server requirements?

Plain PHP ≥ 8.1 with standard extensions: pdo_mysql, openssl, fileinfo, and mbstring, plus any MySQL 5.7+ or MariaDB 10.3+ database. Apache or LiteSpeed (.htaccess included) or nginx. No Composer is needed at runtime.

Which S3 clients and tools can connect to it?

Any S3-compatible client that supports custom endpoints and path-style addressing: official AWS SDKs (PHP, Python/boto3, Node.js, Go, Java), AWS CLI, rclone, Cyberduck, Nextcloud external storage, and backup tools like Duplicati or Borg.

How does installation work?

It installs like WordPress: point your document root at public/, create an empty MySQL database, open https://your-domain.example/_admin/install in your browser, and submit the wizard. The installer automatically writes config.php outside the web root, runs database migrations, creates the admin user, and locks itself permanently.

Ready to host your own S3 storage?

Free, open-source under the MIT license, with zero telemetry and zero runtime dependencies.