Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Once your Ruby application has written the PDF, upload it with AWS SDK for Ruby v3. For a completed file, the simplest path is Aws::S3::Object#upload_file; when you already have an open stream, pass it to Object#put. Both approaches require AWS credentials, permission to write the destination bucket, and a deliberate S3 object key.

Prerequisites

  • Ruby and a project-managed dependency setup (Bundler is recommended).
  • The AWS SDK for Ruby v3 S3 gem: aws-sdk-s3. AWS identifies v3 as its current SDK generation in its documentation.
  • An S3 bucket in the AWS account and region you intend to use.
  • Credentials supplied through the normal AWS credential provider chain, an IAM role, or environment-specific configuration. Do not put access keys in source code.
  • An IAM identity allowed to write the selected key, normally with s3:PutObject and any encryption-related permissions required by your bucket policy.

The PDF-generation step is application-specific. Rails, Sinatra, background jobs, and standalone Ruby programs can all use the same upload code after a PDF exists as a path, File, or Tempfile.

Install and configure the AWS SDK for Ruby

Add the SDK to your Gemfile:

gem "aws-sdk-s3"

Then install it:

bundle install

The SDK can discover credentials from environment variables, shared AWS configuration, instance or container roles, and other standard providers. Keep the bucket name, region, and key separate from credentials, preferably in environment variables or your application’s configuration system:

bucket = ENV.fetch("PDF_BUCKET")
region = ENV.fetch("AWS_REGION", "us-east-1")
key    = "reports/#{report_id}/generated.pdf"

Use a stable, collision-safe key. A key such as reports/123/generated.pdf overwrites that object on a later upload; adding a UUID or version identifier creates a new object instead. Concurrent writes to one key should not be assumed to preserve every version unless S3 bucket versioning or an application-level locking/version strategy is intentionally configured.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
#1 Best Overall

Method 1: Upload a generated PDF by file path

Use upload_file when your PDF generator has completed a file on disk. The object API accepts a string path, Pathname, File, or Tempfile.

require "aws-sdk-s3"

bucket = ENV.fetch("PDF_BUCKET")
region = ENV.fetch("AWS_REGION", "us-east-1")
key = "reports/123/generated.pdf"
pdf_path = "/tmp/generated-report.pdf"

s3_object = Aws::S3::Object.new(
  bucket,
  key,
  region: region
)

s3_object.upload_file(
  pdf_path,
  content_type: "application/pdf"
)

puts "Uploaded s3://#{bucket}/#{key}"

content_type: "application/pdf" sets the object’s HTTP content type explicitly. This helps browsers and downstream clients handle the object as a PDF; do not assume every application-level header will be inferred automatically.

A complete application would replace the example path with the path returned by its PDF generator and handle the upload exception. AWS’s Ruby S3 examples cover both upload_file and Object.put patterns at the official examples page.

Method 2: Upload an open file or Tempfile with Object#put

Use put when you want explicit control over the request body and the file’s lifetime. Open the source in binary mode and close it after the request:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
require "aws-sdk-s3"

bucket = ENV.fetch("PDF_BUCKET")
region = ENV.fetch("AWS_REGION", "us-east-1")
key = "reports/123/generated.pdf"
pdf_path = "/tmp/generated-report.pdf"

s3_object = Aws::S3::Object.new(bucket, key, region: region)

File.open(pdf_path, "rb") do |file|
  s3_object.put(
    body: file,
    content_type: "application/pdf"
  )
end

puts "Uploaded s3://#{bucket}/#{key}"

The block guarantees that a file opened by your code is closed after the request. This method is also suitable for an already-open Tempfile:

require "tempfile"
require "aws-sdk-s3"

pdf = Tempfile.new(["report", ".pdf"])
begin
  # Your PDF generator writes bytes to pdf.
  pdf.write(generated_pdf_bytes)
  pdf.flush
  pdf.rewind

  object = Aws::S3::Object.new(
    ENV.fetch("PDF_BUCKET"),
    "reports/123/generated.pdf",
    region: ENV.fetch("AWS_REGION", "us-east-1")
  )

  object.put(body: pdf, content_type: "application/pdf")
ensure
  pdf.close
  pdf.unlink
end

Rewind a Tempfile if it has just been written or read; otherwise the upload may begin at the current cursor position rather than byte zero. If you pass an open Tempfile to the SDK, your code remains responsible for closing it. You can also pass the completed tempfile’s path while the file still exists.

Choosing between upload_file and put

Situation Better fit Resource responsibility
The generator produced a finished path upload_file(path) The SDK reads the supplied path; your code manages creation and later cleanup.
You already have an open File or Tempfile put(body: io) Your code must position, close, and, for tempfiles, unlink the source.
You need explicit request metadata such as content type Either method with upload options Set content_type deliberately.

Neither choice is universally faster or safer. Select the one that matches the source you already have and the lifetime you can manage clearly.

Large PDFs and multipart uploads

The AWS SDK for Ruby v3 Aws::S3::Object#upload_file documentation lists a default multipart threshold of 104,857,600 bytes (100 MiB). At or above that size, the documented object API default uses multipart upload APIs. This is an SDK setting, not a universal S3 limit: thresholds are configurable, and another abstraction such as TransferManager may expose different controls. Verify the behavior for the SDK version and abstraction in your application.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Multipart transfer can improve resilience for large files because parts are transferred independently, but it also introduces more requests and cleanup considerations if a process stops during an upload. Keep the source file available until the call returns, and monitor failures in your job system. The TransferManager API documentation describes multipart transfer behavior and accepted file source types.

Encryption, access, and object metadata

Keep generated reports private unless your application has an explicit public-access requirement. A downloadable PDF should normally be delivered through an authorized application endpoint or a short-lived presigned URL, not by making the bucket or object public.

If your bucket requires server-side encryption, supply the option that matches its configuration and IAM policy. For example, S3-managed encryption can be requested as follows:

File.open(pdf_path, "rb") do |file|
  s3_object.put(
    body: file,
    content_type: "application/pdf",
    server_side_encryption: "AES256"
  )
end

Use a KMS key only when your bucket and identity are configured for it, and use the corresponding KMS option and permissions. AWS documents body, content type, and encryption parameters in the Bucket API reference. Do not copy an encryption setting blindly into an account with a different bucket policy.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Confirming success and handling failures

An SDK call that returns without raising an exception indicates that the request completed from the client’s perspective. Record the bucket and key, and optionally verify metadata with a head request:

head = s3_object.head
raise "Unexpected type" unless head.content_type == "application/pdf"
puts "Stored #{head.content_length} bytes"

Wrap uploads in error handling appropriate to your application. AWS service errors expose details useful for logs, while user-facing responses should avoid leaking credentials or internal paths:

begin
  s3_object.upload_file(pdf_path, content_type: "application/pdf")
rescue Aws::S3::Errors::ServiceError => e
  logger.error("S3 upload failed: #{e.class}: #{e.message}")
  raise
end

Troubleshooting checklist

AccessDenied or authorization failures

  • Confirm the process is using the intended AWS identity and account.
  • Check permission for the exact bucket and key prefix, including any bucket policy, organization policy, or KMS permissions.
  • Verify that an encryption requirement is satisfied rather than overridden.

NoSuchBucket or region errors

  • Check spelling and account ownership of the bucket.
  • Set the client/object region to the bucket’s actual region; do not infer it from a user’s location.

The uploaded PDF is empty or truncated

  • Ensure PDF generation finished before upload.
  • Flush and rewind a Tempfile or other IO before passing it as body.
  • Keep the source file alive until the SDK call returns.

The browser downloads an unknown file type

  • Set content_type: "application/pdf".
  • Check stored metadata with head; metadata from an earlier object remains if a different upload path did not replace it as expected.

Uploads fail only for large files

  • Check timeout, memory, and worker limits.
  • Review multipart threshold and transfer settings for your SDK version.
  • Make sure interrupted multipart operations are monitored and cleaned up according to your operational procedures.

The same report is replaced unexpectedly

Your key is the object name. Add a report version, timestamp, or UUID when each result must coexist, or deliberately enable bucket versioning and design retrieval around it.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

If your workflow also needs clean website screenshots for documentation or report attachments, ScreenshotNeo provides a one-request screenshot API and an MCP server for AI agents. It accepts the cookie or consent banner before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; bot checks, blank pages, timeouts, failed loads, and cache hits are not billed. Its MCP tools include take_screenshot, get_page_info, and capture_pdf.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Example request (see the ScreenshotNeo API documentation):

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

The same endpoint can be called from Ruby, Python, or Node.js:

require "net/http"
require "uri"

uri = URI("https://api.screenshotneo.com/v1/shot")
uri.query = URI.encode_www_form(access_key: "YOUR_API_KEY", url: "https://stripe.com")
File.binwrite("shot.webp", Net::HTTP.get(uri))
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

ScreenshotNeo includes 1,000 screenshots per month free with no card; paid plans start at $5 for 3,000. Create a free ScreenshotNeo account.

Further AWS references

Frequently Asked Questions

Can I upload a Ruby Tempfile directly to S3?

Yes. Pass the tempfile as body to Object#put or pass it to upload_file; rewind it first when necessary and close and unlink it after the upload.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Does S3 automatically make an uploaded PDF downloadable?

S3 stores the object, but your application still needs an access design. Keep it private unless public access is intentional, and serve it through authorization or a presigned URL.

When should I use multipart upload?

The Ruby v3 object API documents multipart behavior for upload_file at or above its configurable 104,857,600-byte default threshold. Confirm the current setting for your SDK abstraction.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.