Upload to AWS S3 Using Axios Pre-Signed URLs
Uploading files directly to Amazon S3 via pre-signed URLs removes
load from your application server while keeping S3 credentials secure.
This guide covers how to properly configure the Axios HTTP client to
execute PUT requests against S3 pre-signed upload URLs,
manage upload progress, handle required headers, and bypass common
issues like authorization conflicts and signature mismatches.
The Core Upload Flow
When an S3 pre-signed upload URL is generated on the server, it is
bound to a specific HTTP method (almost always PUT), a
destination path, and optionally specific headers such as
Content-Type.
To complete the upload, Axios must send a PUT request
with the exact binary/blob data and matching headers.
Basic Browser Configuration
In browser environments, you pass the native File or
Blob object directly into the data field of an
Axios PUT request.
import axios from 'axios';
async function uploadFileToS3(presignedUrl, file) {
try {
const response = await axios.put(presignedUrl, file, {
headers: {
'Content-Type': file.type || 'application/octet-stream',
},
onUploadProgress: (progressEvent) => {
const percentCompleted = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
);
console.log(`Upload progress: ${percentCompleted}%`);
},
});
return response.status === 200;
} catch (error) {
console.error('Error uploading file to S3:', error);
throw error;
}
}Node.js Configuration
For Node.js environments, use a Buffer or a readable
stream with fs.createReadStream().
const axios = require('axios');
const fs = require('fs');
async function uploadFileStream(presignedUrl, filePath, mimeType) {
const fileStream = fs.createReadStream(filePath);
const fileStats = fs.statSync(filePath);
return axios.put(presignedUrl, fileStream, {
headers: {
'Content-Type': mimeType,
'Content-Length': fileStats.size,
},
maxBodyLength: Infinity,
maxContentLength: Infinity,
});
}Critical Axios Configuration Rules
1. Match Headers Exactly
If the pre-signed URL was generated specifying a
ContentType or server-side encryption header (e.g.,
x-amz-server-side-encryption), those exact headers must be
included in the Axios configuration. Conversely, do not add headers that
were not part of the signed signature, as S3 will reject the request
with a 403 Forbidden (SignatureDoesNotMatch)
error.
2. Avoid Global Interceptors
If your application uses global Axios interceptors that attach
Authorization: Bearer <token> or other API headers to
every request, S3 will reject the upload. Use an isolated Axios instance
for S3 operations:
// Create a dedicated instance without app-specific interceptors
const s3Client = axios.create();
// Ensure default headers are stripped if inherited
delete s3Client.defaults.headers.common['Authorization'];
await s3Client.put(presignedUrl, file, {
headers: {
'Content-Type': file.type,
},
});3. Handle Large File Limits in Node.js
By default, Axios limits request body sizes in Node.js environments.
When uploading large files, explicitly set maxBodyLength
and maxContentLength to Infinity in your
request configuration.
4. Enable S3 CORS
Ensure your AWS S3 bucket has a Cross-Origin Resource Sharing (CORS)
policy configured to allow PUT requests and expose needed
headers for browser-based uploads:
[
{
"AllowedHeaders": ["*"],
"AllowedMethods": ["PUT"],
"AllowedOrigins": ["https://yourdomain.com"],
"ExposeHeaders": ["ETag"]
}
]