Cyberduck S3 uploads fail while bucket listing still works

How can I determine which S3 permission Cyberduck is missing when it can connect and list a bucket but fails during an upload? I’m setting this up for my team’s shared work archive, and I have already confirmed that the saved connection points to the intended bucket and that browsing existing folders works.

When I try to upload a file, Cyberduck starts the transfer and then reports an access-denied response. Creating a folder fails as well, while downloading an existing file succeeds. I have read that listing, downloading, uploading, and creating paths can require different S3 permissions. Which operation does Cyberduck attempt first, and which log details would identify the missing permission?

My assessment of the tool is that while it can handle the occasional file management tasks for S3, it cannot handle heavy-duty uploads and downloads. I got there by separating everyday browsing and file operations from the more involved tasks which require better transfer performance.

I tested Cyberduck as a general file tool rather than expecting it to replace a native command-line workflow. The procedure was straightforward: connect, browse buckets, create folders, upload files, download them again, and manage remote content without working directly in a terminal.

Where it fits

For the routine-type operations, I find the interface quite comfortable, and the performance of the tool is surprisingly decent for such a basic-level utility. The option for multipart uploads also helps to complete the task faster, especially with larger files. However, I would leave the scope of responsibilities limited to the listed operations. I think it makes much more sense to use Cyberduck for occasional transfers of files when needed rather than for day-to-day operations and maintenance of the bucket.

I wouldn’t treat convenience as proof that every workload will perform equally well. Reports document some limitations, and specific bulk-transfer tasks or very large files can slow down significantly compared with native CLI tools. There’s a command-line interface for Cyberduck if automation is needed, but for demanding transfer work I’d still compare it against the usual CLI process before committing.

The SFTP alternative

I’d make a different choice for regular SFTP access. CloudMounter. mounts remote servers as local drives, so files can be handled directly through Finder on a Mac. That removes the extra step of opening a separate FTP or SFTP client every time, which is kinda useful for everyday work.

So my final split is simple: I’d use S3 through Cyberduck for casual management, native CLI tools for large or bulk transfers, and CloudMounter for frequent SFTP work in Finder.

13 Likes

This is not a transfer-performance problem. Listing and writing use separate S3 permissions, so successful browsing only proves that the credentials have s3:ListBucket. It says nothing about s3:PutObject, which is the first permission to check for the archive’s object prefix.

Reproduce the failure with a small file, then inspect Cyberduck’s transfer log for the AWS error and request ID. Check the matching S3 data event in CloudTrail. The event name tells you which API call was denied. If small uploads work but larger ones fail, look at multipart permissions: s3:ListBucketMultipartUploads on the bucket, plus s3:ListMultipartUploadParts and s3:AbortMultipartUpload on objects. KMS-encrypted uploads may need kms:GenerateDataKey and kms:Decrypt too.

There is another easy-to-miss failure: if the bucket has ACLs disabled, set Cyberduck’s default S3 ACL to None. Otherwise it may try to attach an ACL that the bucket rejects. I still reach for CloudMounter when several storage accounts need their connection profiles kept together, although I am not thrilled about another desktop application retaining cloud credentials. Changing clients will not fix the policy, so identify the denied API call before moving the team over.

Do not keep retrying against a shared archive until you check whether the object was actually created. Cyberduck can report the transfer as failed because a secondary request was denied even though the file data reached S3. Repeated attempts may leave duplicate versions or unfinished multipart uploads.

The quickest isolation test is to perform a minimal PutObject with the same credentials, endpoint, bucket, and key prefix:

aws s3api put-object \
 --bucket YOUR-BUCKET \
 --key shared-archive/permission-test.txt \
 --body permission-test.txt \
 --profile YOUR-PROFILE

If that fails, the returned AWS error is more useful than Cyberduck’s general transfer message. If it succeeds, plain s3:PutObject is allowed and Cyberduck is making another request or adding headers that trigger a different permission check. Compare the command’s key with the exact destination Cyberduck uses, including capitalization and the trailing path. Prefix conditions in bucket policies are easy to get subtly wrong.

@rust_serg58 is right about checking the denied API call, but CloudTrail data events are not necessarily enabled for the bucket. If they were not configured before the test, there may be no matching object-level event to inspect. In that case, turn them on temporarily, reproduce once with a uniquely named file, and record the timestamp, AWS request ID, object key, and principal ARN from the Cyberduck log.

Do not limit the review to the IAM policy attached to the user or role. The effective denial can come from:

  • the bucket policy
  • an AWS Organizations service control policy
  • a permissions boundary
  • a role session policy
  • a VPC endpoint policy
  • a resource policy that allows a different prefix or principal
  • a condition on encryption, source network, object tags, or required request headers

An explicit deny in any of those layers wins over an allow. This is why simply adding s3:* to a test statement sometimes changes nothing.

Check the bucket after the failed attempt too. If the object exists, look at the next request in Cyberduck’s log rather than treating the upload itself as denied. If no object appears, test a tiny file and then a larger one to separate a basic write failure from the multipart path. For the shared archive after the access policy is settled, I would use CloudMounter, but I would still run the AWS CLI comparison first because it gives you a clean baseline without client-specific follow-up calls.

Once you have the exact denied action, grant it only on the intended bucket or object prefix. Avoid solving this with broad S3 access, especially for a team archive where an accidental delete or overwrite has a much larger blast radius.

If the object appears in S3 after Cyberduck reports failure, the missing permission may be read-related rather than write-related. Cyberduck can finish the PutObject request and then issue a HEAD request to confirm the object or read its metadata. That follow-up requires s3:GetObject, so an upload-only policy can produce a misleading failed-transfer message.

Use a new filename, enable Cyberduck’s request log, and reproduce the problem once. Read the requests in order rather than relying on the final popup. The denied request usually maps directly to the permission:

  • PUT /prefix/file means check s3:PutObject.
  • HEAD /prefix/file means check s3:GetObject.
  • A request containing ?acl means check s3:PutObjectAcl, unless ACLs are disabled.
  • A request for ?ownershipControls means Cyberduck needs s3:GetBucketOwnershipControls.

Pay attention to the AWS error code as well as the HTTP status. AccessDenied points toward policy evaluation. AccessControlListNotSupported does not mean you should grant another permission. It means Cyberduck attempted to send an ACL to a bucket that rejects ACLs, so its default ACL should be set to None. @rust_serg58’s ACL warning matters here because those two failures can look nearly identical in the interface.

For a shared archive, decide whether the credentials are supposed to read uploaded files or only deposit them. Granting s3:GetObject may make Cyberduck’s verification succeed, but it also gives that principal access to object contents within the allowed resource pattern. If this is deliberately a write-only drop location, confirm the successful PUT from the log and avoid expanding the policy merely to silence a client-side verification failure.

Do not grant s3:PutObject against only the bucket ARN. That is a common policy mistake because s3:ListBucket uses:

arn:aws:s3:::YOUR-BUCKET

Object writes require an object ARN, such as:

arn:aws:s3:::YOUR-BUCKET/shared-archive/*

So Cyberduck can list perfectly while every upload is denied, even though both actions appear in the same policy. Check the exact key shown in its log, including case and any folder prefix, then compare it with the Resource value for s3:PutObject.

The IAM Policy Simulator is useful for this narrow check. Simulate s3:PutObject against the full object ARN rather than the bucket ARN. A denied result usually exposes a missing wildcard or prefix mismatch quickly. An allowed result is not final proof because bucket policies and organization-level controls can still block the request, but it rules out the basic identity-policy error without loosening access on the shared archive.

Don’t grant s3:* just to make Cyberduck stop complaining. First confirm the actual principal with aws sts get-caller-identity using the same credentials, since testing one role while Cyberduck quietly uses another is a wonderfully efficient way to debug the wrong policy; then match the denied request in its log to that principal.

Nine times out of ten this turns out to be a single missing action scoped to the wrong ARN, and once you actually read the error it takes two minutes to fix. The trap is treating it like a mystery instead of a lookup.

The advice to read Cyberduck’s requests in order is the right instinct, and @tom1999’s mapping of HEAD to GetObject is the kind of thing people miss for hours. But before you go turning on CloudTrail data events, check whether the error code you get back is even a permission error. A region or endpoint mismatch in the bookmark produces failures that look a lot like AccessDenied at a glance. You connect fine, listing works because it gets redirected, then a signed PutObject to the wrong regional endpoint comes back with a signature or authorization complaint that has nothing to do with your IAM policy. Confirm the bucket’s region matches what Cyberduck is pointed at first. Cheap check, saves you editing policies that were never broken.

One thing nobody has raised: Block Public Access and the bucket’s object ownership setting can quietly change what ‘correct’ even means here. If ownership is set to bucket-owner-enforced, ACLs are off entirely, and Cyberduck sending its default canned ACL will get bounced with the exact error @rust_serg58 and @tom1999 described. So set the client ACL to None and stop fighting it. That single setting resolves a surprising share of these ‘upload denied but listing works’ threads, and it costs you nothing in policy scope.

For the shared archive itself, I’d keep the debugging creds separate from whatever the team uses day to day. Test with a throwaway profile that has exactly one action granted, watch it succeed, then hand the team a policy that only allows the prefix. On the client side, CloudMounter is worth a look for one boring reason people overlook: it can encrypt files locally before they ever hit the bucket, so the archive stays readable only through the mount. Not a fix for your permission problem, just a nice thing to have once the access side is settled.

Check whether the credentials Cyberduck is holding are long-lived access keys or a temporary session. If your team authenticates through SSO or an assumed role, the bookmark can list the bucket fine right after you log in and then fail on an upload a while later because the session token expired in between. Cyberduck surfaces that as a failed transfer, not as an expired-auth message, and people go straight to rewriting IAM policies that were never the problem. Easy to rule out: reconnect fresh, upload immediately, and see if it works before the token ages out.

The request-order reading @tom1999 laid out is the actual answer to your question, and the HEAD-to-GetObject mapping is the specific thing that traps people for an afternoon. @not_a_wolf’s bucket-ARN versus object-ARN split is the other one I’d bet money on before touching anything fancy. Between those two you’ve probably already found it. I’d hold off on flipping CloudTrail data events on as a first move like some replies suggest, because @smarthacker7552sync is right that a region mismatch throws errors that look like AccessDenied but aren’t, and enabling data events costs you time and a bit of logging spend for a test you might not need.

On CloudMounter, I’d push back a little on the idea that it belongs in this conversation at all right now. Mounting the bucket as a Finder drive is exactly the wrong thing while you’re debugging, because the mount abstracts away the per-request behavior you need to see. The whole fix here depends on reading which call got denied, and a filesystem layer hides that from you. Once the policy is settled it’s a fine convenience for the team, just not the tool you reach for mid-diagnosis.

My rule of thumb: keep debugging on plain keys with the request log open until you’ve watched one upload succeed, then narrow the policy to the prefix and only then hand the team whatever client they’ll actually live in. Solve the permission with the boring client first, add convenience later.