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

The fix is to stop treating every image as a public/ file. In Rails, public/images/header.png, an image managed by the asset pipeline, and an uploaded file use different path rules. Use a filename relative to public/images for public files, a logical asset name for pipeline files, and the storage library’s real path or an accessible URL for uploads. Then inspect the HTML handed to wkhtmltopdf and verify that the external PDF process can read every image.

Why the path becomes public/public

wicked_pdf_image_tag prepares image references for wkhtmltopdf, the executable that renders the PDF. The wicked_pdf maintainers note that wkhtmltopdf runs outside the Rails application, so the normal browser layout and Rails request context are not automatically available to it.

That is why adding public/ to an argument can be wrong. Rails already treats public/images as the public-images root. If a helper or URL builder adds that root again, a request such as /public/images/header.png can become a duplicated path such as public/public/images/header.png. The exact symptom depends on the helper, Rails version, and how the HTML is rendered; the reported duplicated-directory case is a symptom to diagnose, not a rule that applies to every application.

First identify where the file really lives

  1. Public directory: the file is physically under public/images or another directory below public.
  2. Asset pipeline: the source is under app/assets/images or is supplied by another asset backend such as Webpacker.
  3. Upload storage: a user-uploaded file is managed by Active Storage, CarrierWave, Paperclip, or another library and may not be stored under the original client filename.

Check spelling, extension, and case. Linux deployments distinguish Header.png from header.png. Also confirm that the PDF-rendering process runs on the same host and can access the path you intend to generate.

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

Fix a static image in public/images

For a file at public/images/header.png, Rails documents the public-images form:

<%= image_tag "header.png", alt: "Header" %>

Use a filename relative to public/images; do not prepend public/ to that argument. If the file is under another public subdirectory, use the path relative to public that your application’s helper expects. The important check is the generated src, not the helper name alone.

Render the view as HTML or use wicked_pdf’s show_as_html debugging option and inspect the resulting <img src>. In HTML-debug mode, wicked_pdf may show file:/// references. Browser security rules for local files can make that preview differ from the actual PDF render, so use it to inspect the string and then test the PDF itself.

When a local file needs explicit access

Recent wkhtmltopdf builds can restrict local-file access. If the generated reference is a local filesystem URL, enable local-file access in the wicked_pdf configuration appropriate to your installed binary and allow only the directory required by the application. The wicked_pdf documentation shows allowing Rails.root/public; use the setting names supported by your wicked_pdf and wkhtmltopdf versions.

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

Fix an image managed by the asset pipeline

An image under app/assets/images is not a public-directory file. Pass its logical asset name through Rails’ asset helpers, and do not add an /assets/ prefix to a wicked_pdf helper argument. The wicked_pdf README documents that supplying that prefix can produce an asset-name error.

Production PDF rendering must have the assets available. Precompile the images and styles used by PDF views during deployment, and verify that the generated HTML points to the fingerprinted asset or to a file that wkhtmltopdf can read. Development may serve assets dynamically while production expects precompiled files, so a view that works locally can fail after deployment.

Webpacker and other backends

If the application uses Webpacker, use the corresponding wicked_pdf pack-path helper, such as wicked_pdf_asset_pack_path, rather than assuming the Sprockets helper is correct. Propshaft, Sprockets, and Webpacker differ in naming and compilation behavior. Confirm the Rails and asset-backend versions before changing the template.

Fix an uploaded image

Do not build a path from image.original_filename alone. That value is the name supplied by the client; it is not necessarily the stored filename or directory. Resolve the file through the upload library’s API, or generate an absolute URL that the PDF process can fetch.

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.
  • For local storage, obtain the library’s actual stored path and check filesystem permissions.
  • For object storage or a CDN, generate a URL valid from the machine running wkhtmltopdf, including any required expiration or authentication.
  • For private files, ensure the renderer can authenticate; a URL that works in the user’s browser may not work in a background job.

The exact Ruby expression depends on the storage library and configuration, so substituting a guessed method can create a second failure. Inspect the library’s resolved path or URL and test it from the PDF worker.

Inspect the rendered HTML before changing helpers

  1. Generate the PDF view as HTML with wicked_pdf’s HTML-debug option.
  2. Find every <img> and record its complete src.
  3. Classify each source as an HTTP(S) URL, a file:/// URL, a root-relative URL, or a relative URL.
  4. Open or test each source from the same host, container, user account, and working directory used by wkhtmltopdf.
  5. Render the PDF again after correcting the first failing source.

Validate all images, not only the one named in the exception. The wicked_pdf documentation warns that one missing image can affect other images in the resulting PDF.

Choose the path strategy by storage location

Image location Template strategy Deployment check
public/images or another public path Use a path relative to the public root, such as header.png; inspect the generated URL or file path. Confirm local-file permissions or renderer access to the URL.
app/assets/images or another asset system Use the logical asset helper name; use a pack-path helper for Webpacker where applicable. Precompile PDF assets and account for the Rails asset backend.
Uploaded file Use the storage library’s resolved path or an accessible URL. Check the real stored location, permissions, authentication, and URL lifetime.

Common failures and precise fixes

The path contains public/public

Cause: the public root was included both in the argument and in helper or URL resolution. Fix: remove the literal public/ prefix and pass the filename relative to public/images, then inspect the generated src.

The browser shows the image but the PDF does not

Cause: the browser has Rails session context or network access that the external wkhtmltopdf process lacks. Fix: use an absolute, renderer-accessible URL or an allowed local file path; verify credentials, DNS, firewall rules, and local-file access.

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

Adding /assets/ raises an asset error

Cause: a logical asset helper expects the logical name, not the output directory prefix. Fix: pass the logical filename and precompile the asset for production.

It works in development but fails in production

Cause: production compilation, fingerprinting, permissions, or the wkhtmltopdf binary differs. Fix: precompile PDF assets, inspect the production HTML, verify the deployed file exists, and compare wicked_pdf and wkhtmltopdf versions.

An uploaded filename resolves to nowhere

Cause: original_filename identifies the client’s name, not necessarily the stored object. Fix: ask the storage library for its actual path or generate a renderer-accessible URL.

One missing image causes several to vanish

Cause: the renderer can react poorly to a missing resource. Fix: test every image source individually and remove, correct, or make accessible any failing reference.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Reliability and deployment checklist

  • Use the helper that matches the file’s storage location.
  • Never assume an original upload filename is a filesystem path.
  • Inspect final HTML rather than inferring the URL from template code.
  • Precompile assets used by PDF views before production jobs run.
  • Permit only the required local directory when enabling local-file access.
  • Run a PDF render under the same OS user and environment as the production worker.
  • Check all image sources after any asset, storage, domain, or wkhtmltopdf change.

Or skip the browser setup

If you only need a clean screenshot or PDF of a web page rather than a Rails-generated PDF, ScreenshotNeo makes the capture with one request. It accepts cookie and consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be disabled. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the page verdict and billing status in headers. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients.

cURL:

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

Python:

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)

Node.js:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

See the ScreenshotNeo API documentation for options such as full-page capture, CSS selectors, waits, custom headers, cookies, device presets, PDF settings, caching, asynchronous jobs, and bulk capture. The Free plan includes 1,000 screenshots each month without a card; paid plans start at $5 for 3,000. Create a free ScreenshotNeo account.

Frequently Asked Questions

Should I use image_tag or wicked_pdf_image_tag?

Choose according to the image location and the HTML produced for wkhtmltopdf. The decisive test is whether the final source is accessible to the external renderer, not the helper name by itself.

Why does a relative URL behave differently in a PDF job?

wkhtmltopdf runs outside Rails and may have a different working directory, network context, credentials, and local-file policy. Use an inspected absolute URL or an explicitly allowed local path.

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

Do I need to change the upload’s original filename?

No. Resolve the stored object through the upload library. Renaming the client filename does not by itself tell wkhtmltopdf where the file is.

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.