AWS CLI

Enabling S3 Bucket Versioning with a One-Line Bash Script

How I automated turning on versioning for my S3 bucket using the AWS CLI's s3api command — chmod hiccup and all

In this step of my S3 setup series, I write a small bash script that flips versioning on for my bucket using the AWS CLI's s3api command group. I also hit — and fix — the classic 'forgot to chmod it' error, so you don't have to guess what that looks like when it happens to you.

Why versioning matters before I do anything else with this bucket

Before I put a single real file into my S3 bucket, I wanted to turn on versioning. Once it's enabled, every time I add, replace, or delete an object, S3 keeps the history instead of just overwriting or losing it. That means if I accidentally upload a bad version of a file, or delete something I needed, I can go back and recover the previous copy instead of it just being gone.

This is step two in a bucket setup process I'm working through — step one was creating the bucket itself. Now that it exists, the next logical move is to lock in this safety net before anything else touches it. I think of it as the digital equivalent of turning on autosave before you start editing a document: you want it running before you need it, not after.

I'm doing this the same way I did the bucket creation — with a small shell script rather than clicking through the AWS console. It's slower the first time, but it means the exact steps are captured, repeatable, and easy to hand off or rerun later if I ever need to recreate this setup from scratch.

Creating a new script file

I hopped into VS Code and, from the integrated terminal, used touch to create a new empty file. I'm following a numbered naming convention for these scripts so they sort in the order they're meant to run — something like 102-enable-versioning.sh, picking up right after whatever number I used for the bucket creation script before it.

That numbering might seem like a small detail, but it's saved me from confusion more than once. When you've got a folder full of setup scripts, having them sort in execution order means you never have to stop and think 'wait, which one do I run first?' It's a cheap habit that pays off every time I come back to this project.

Once the file existed, I opened it in the editor — completely blank at this point — and got ready to write the actual logic.

Writing the script itself

I started the file the way I start basically every bash script: with a #!/bin/bash shebang line at the top, followed by a setting that tells the script to exit immediately if any command inside it fails. That second part matters more than it might seem — without it, a script can plow ahead after something breaks and give you a false sense that everything worked. I'd rather have the script stop cold and show me the error than silently continue.

Next I defined a bucket_name variable and set it to match the exact bucket I created in the previous step. This is the one line where you have to be careful — the name has to match your actual bucket precisely, since S3 bucket names are globally unique and case-sensitive. I'll be honest that when I said the name out loud while recording this, I wasn't fully sure I had the trailing digits right. That's a good reminder in general: don't trust a spoken or half-remembered bucket name, go copy the exact string from wherever you created it (your AWS console, your previous script, or your notes) and paste it in.

For the actual command, I reached for the AWS CLI's s3api command group rather than the higher-level s3 commands. It's worth knowing these are two different families of commands in the AWS CLI: the plain s3 commands are simplified for common operations like copying files or syncing folders, while s3api gives you direct, one-to-one access to the underlying S3 API operations — things like setting bucket policies, configuring lifecycle rules, or, in this case, toggling versioning. If you've only ever used aws s3 cp or aws s3 sync, s3api will feel a bit more verbose, but it's also more precise about exactly what it's doing.

The specific call I used is put-bucket-versioning, pointed at my bucket, with the versioning status set to enabled. I leaned on my editor's autocomplete to fill in the exact flag names rather than typing them from memory — which is a nice little workflow tip on its own. If you're not sure of the precise syntax for a CLI subcommand, autocomplete (or --help on the command itself) will often get you there faster and more reliably than trying to recall it. That said, because I filled it in this way rather than writing it out flag by flag, I'd encourage you to double-check the exact put-bucket-versioning syntax against the AWS CLI documentation before you copy this pattern into your own scripts — I want to be upfront that the precise flags deserve a quick verification pass rather than being taken purely on faith from a screencast.

Running it — and hitting the error I half-expected

With the script written, I switched back to the terminal to run it. And right on cue, I hit an error — I'd forgotten to make the script executable. If you've spent any time writing shell scripts, this one is almost a rite of passage: you write the file, try to run it, and the shell tells you it doesn't have permission to execute it.

The fix is one command: chmod +x on the script file. That flips on the executable bit for the file, which is a required step before the shell will treat it as something it can run directly rather than just a text file. It's easy to forget, especially when you're used to interpreted scripts running via an explicit command like bash script.sh or python script.py — but if you want to invoke your script directly with ./script.sh, executable permission is non-negotiable.

I think it's actually useful that this happened on camera rather than being edited out, because it's exactly the kind of error you're going to run into yourself the first few times you write and run your own scripts. Now you'll recognize it immediately instead of being thrown by it.

After running chmod +x, I reran the script, and this time it executed cleanly. The output came back confirming the bucket's versioning status was now 'Enabled' — exactly what I was after. No errors, no ambiguity, just a clean confirmation that the setting had taken effect.

What this sets up for later

With versioning on, this bucket is now in a state where I can add, overwrite, or delete objects without worrying that a mistake is unrecoverable. Every version sticks around until I explicitly manage or expire it — which becomes relevant down the line if I want to add lifecycle rules to clean up old versions automatically, but that's a concern for a future step, not this one.

The bigger pattern here, though, is less about versioning specifically and more about the workflow: small, numbered, single-purpose scripts that each configure one aspect of the bucket. It's slower than clicking around in the console, but every step is documented in code, and I can rerun the whole sequence on a fresh bucket without having to remember what I clicked last time.

Conclusion

If you're following along, try writing this script yourself against your own bucket — swap in your actual bucket name rather than reusing any example name, double-check the put-bucket-versioning flags against the AWS CLI docs before you run it, and don't be surprised if you hit the same chmod error I did. It's a normal part of the process, not a sign you did something wrong.

Official AWS references

KEEP PRACTICING THE DECISION

Learn the concept, then test the edge case.

The Solutions Architect Associate course in TutorialRepo combines guided lessons, scenario questions, review, and course-aware explanations.

  All articles