This post is archived and may contain outdated information. It has been set to 'noindex' and should stop showing up in search results.
Simple PHP Code for Uploading a File to an Amazon S3 Bucket - Signature Version 2
Sep 2, 2016ProgrammingComments (4)
Note: This code uses Amazon AWS Signature Version 2, which is not available on any S3 Regions created after January 30, 2014. It is recommended to use Version 4. Please view the AWS Version 4 post here.


Amazon offers a PHP SDK for handling AWS and S3 requests, but it weighs in at over 500 files and nearly 5MB. If you just want to upload a file to an S3 bucket using PHP, you can create the HTTP POST request yourself using only about 30 lines of code. This is also useful if you want to understand how the request and authorization process work.

Sample Code
This is sample code to help you understand and test uploading to Amazon S3. If you want to use this in your project, you should probably modify this and put it into a function or method. I have it formatted like this specifically to help show how the process works.

Read the inline code comments for an explanation of each section:


// These are used in multiple places in the request. Replace the
// values with ones appropriate to you.
$accessKeyId = 'YOUR_ACCESS_KEY_ID';
$secretKey = 'YOUR_SECRET_KEY';
$bucket = 'YOUR_BUCKET_NAME';
$region = 'BUCKET_AMAZON_REGION'; // us-west-2, us-east-1, etc
$acl = 'ACCESS_CONTROL_LIST'; // private, public-read, etc
$filePath = 'path/to/file.jpg';
$fileName = 'myimage.jpg';
$fileType = 'image/jpeg';

// Amazon requires a base64-encoded POST policy written in JSON.
// This tells Amazon what is acceptable for this request. For
// simplicity, we set the expiration date to always be a day in
// the future. The two "starts-with" fields are used to restrict
// the content of "key" and "Content-Type", which are specified
// later in the POST fields. Again for simplicity, we use blank
// values ('') to not put any restrictions on those two fields.
$policy = base64_encode(json_encode(array(
'expiration' => gmdate('Y-m-d\TH:i:s\Z', time() + 86400),
'conditions' => array(
array('acl' => $acl),
array('bucket' => $bucket),
array('starts-with', '$key', ''),
array('starts-with', '$Content-Type', '')

// A base64-encoded HMAC hashed signature with your secret key.
// This is used so Amazon can verify your request, and will be
// passed along in a POST field later.
$signature = hash_hmac('sha1', $policy, $secretKey, true);
$signature = base64_encode($signature);

// Pass in the full URL to your Amazon bucket. Set
// RETURNTRANSFER and HEADER true to see the full response from
// Amazon, including body and head. Set POST fields for cURL.
// Execute the cURL request.
$url = 'https://' . $bucket . '.s3-' . $region . '';
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, array(
'key' => $fileName,
'AWSAccessKeyId' => $accessKeyId,
'acl' => $acl,
'policy' => $policy,
'Content-Type' => $fileType,
'signature' => $signature,
'file' => new CurlFile(realpath($filePath), $fileType, $fileName)
$response = curl_exec($ch);

// If Amazon returns a response code of 204, the request was
// successful and the file should be sitting in your Amazon S3
// bucket. If a code other than 204 is returned, there will be an
// XML-formatted error code in the body. For simplicity, we use
// substr to extract the error code and output it.
if (curl_getinfo($ch, CURLINFO_HTTP_CODE) == 204) {
echo 'Success!';
} else {
$error = substr($response, strpos($response, '<Code>') + 6);
echo substr($error, 0, strpos($error, '</Code>'));

If you aren't receiving any response at all, check if curl_exec($ch) is returning false. If it is, chances are it's due to an SSL issue. You can check the error at curl_error($ch). For testing purposes, you can set these two additional cURL options and see if it works:

curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);

But note that doing this is very insecure. Read this Stackoverflow post and this blog post.
Comments (4)
Add a Comment
NG   Mar 29, 2019
Hi there, Looks like there is an error here: array('starts-with', '$key', ''), array('starts-with', '$Content-Type', '') Same param names - different value. And $key is not defined.
Nick Vogt   Apr 12, 2017
Thank you for the notice. I just created a new post with the AWS4 version of this code here: I will update this post accordingly.
Xavi Esteve   Apr 03, 2017
Same here, this method is deprecated and will stop working as Amazon updates their servers (already stopped working in EU Frankfurt and EU London). It is strongly encouraged to use AWS Signature v4
Ilya Glebov   Oct 12, 2016
Doesn't work for Frankfurt location. Error: InvalidRequest The authorization mechanism you have provided is not supported. Please use AWS4-HMAC-SHA256