📍 Dhanmondi, Dhaka-1205

413 Request Entity Too Large: Why Your Oracle APEX Import Fails Behind nginx (and the Exact Fix)

You click Import in APEX App Builder, pick your export file, and get a bare white page: 413 Request Entity Too Large, with nginx/1.14.1 printed underneath. The error mentions APEX in the URL, so that is where everyone starts looking. It is the wrong place. The 413 came from your reverse proxy, which rejected the upload before ORDS or APEX ever saw a single byte. This guide gives you the one-line fix, explains exactly where to put it, and shows you how to prove it worked instead of hoping it did.

Key Takeaways

  • A 413 in front of APEX is almost always nginx, not APEX or ORDS - the footer of the error page names the culprit.
  • nginx defaults client_max_body_size to just 1 MB, and a real APEX application export is usually bigger than that.
  • Put the directive in the http block so every virtual host inherits it - including an ORDS proxy defined in a separate conf.d file.
  • Raise the timeouts too. Fix only the size and a large import can still die on proxy_read_timeout, which looks like a completely different bug.
  • Use systemctl reload nginx, never restart - reload swaps workers without dropping live APEX sessions.
  • Prove it: POST a 5 MB dummy file through the proxy. A 404 means the limit is live; a 413 means it is not.
  • Never judge whether nginx reloaded by the master process start time - reload keeps the same master and only respawns workers.
A closed wooden gate in a narrow stone passage - the reverse proxy blocking an oversized APEX upload before it reaches ORDS
Your upload never reached APEX. It was stopped at the gate. Photo: Ayşegül Aytören / Pexels

1. What Does 413 Request Entity Too Large Actually Mean?

413 Request Entity Too Large is an HTTP status code meaning the server refused to process your request because the body exceeded its configured maximum size. The server never read the whole upload. It checked the declared content length, decided it was too big, and answered immediately.

That last detail matters more than it looks. The rejection happens at the very front of the request path, which is why nothing appears in your APEX or ORDS logs.

So the error is not telling you your application is too large for APEX. It is telling you a middleman has a smaller idea of "too large" than you do.

2. Why It Is nginx - Not APEX, Not ORDS

Look at the error page again. A plain white page with the version string nginx/1.14.1 at the bottom is nginx's built-in error output, not an APEX screen.

APEX errors look like APEX: styled pages, an error code, usually a message you can search. ORDS errors are typically JSON or a Java stack trace. A bare centred heading with a server version underneath is the signature of the proxy itself answering.

A typical APEX deployment has more layers than people hold in their heads at once:

  1. The browser posts your export file to wwv_flow.accept.
  2. nginx receives it on port 80 or 443 and checks the size against client_max_body_size.
  3. ORDS - usually inside Tomcat - receives the proxied request and has its own body limits.
  4. The database finally runs the import through the APEX engine.

Every one of those layers can reject an upload, and each does it with a different error. Learning to read which layer answered saves hours, exactly the same way reading TNS errors as a connection chain turns listener problems into five-minute fixes.

The 413 tells you it was layer two. Start there and stop guessing.

3. The Fix: Where client_max_body_size Belongs

One directive clears it. Where you put it determines whether it actually applies.

On a stock Red Hat or Oracle Linux install, the ORDS proxy usually is not in nginx.conf at all. It lives in its own file, pulled in by an include:

# /etc/nginx/conf.d/ords.conf - the real vhost
server {
    listen 80 default_server;
    server_name _;
    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host              $host;
        proxy_set_header X-Real-IP         $remote_addr;
        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

You could add the directive inside that server block. There is a better spot.

Put it in the http block of /etc/nginx/nginx.conf instead, and every virtual host inherits it - including anything added to conf.d later:

http {
    include             /etc/nginx/mime.types;
    default_type        application/octet-stream;

    # raised for APEX application imports through ORDS
    client_max_body_size 500M;
    client_body_timeout  300s;
    proxy_read_timeout   300s;
    proxy_send_timeout   300s;

    include /etc/nginx/conf.d/*.conf;
}

Then validate before you touch the running service:

cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak-$(date +%F)
nginx -t
systemctl reload nginx

Never skip nginx -t. A single missing semicolon means the reload fails and you are debugging a dead web tier instead of an import.

One warning about inheritance. The most specific block wins, so a small client_max_body_size sitting inside your ORDS server block silently overrides the generous one you just set globally. Check for it:

grep -rn "client_max_body_size" /etc/nginx/

4. Is 500M the Right Number?

Probably not, and it is worth thinking about for ten seconds rather than copying mine.

A typical APEX application export is a few megabytes. Even a large one with many pages and plenty of static files rarely passes 50 MB.

So 500M is generous headroom, not a requirement. The tradeoff is that whatever you set is also what an attacker may post at you, and each oversized request consumes worker memory and disk while nginx buffers it.

My advice: use 100M on an internet-facing APEX instance. Reserve the very large values for internal build servers where you control who can reach the port.

Ethernet cables patched into a network switch panel, representing the nginx reverse proxy layer sitting between the browser and ORDS
The proxy layer is a hop most people forget they installed. Photo: Sejio402 / Pexels

5. Why the Timeouts Matter as Much as the Size

This is the part that sends people back to the forums a second time, convinced the fix did not work.

You raise client_max_body_size, the 413 disappears, and the import now runs for ninety seconds and dies with a 504 Gateway Timeout. Different error, same root cause: the request is simply big and slow.

Three directives govern that:

  • client_body_timeout - how long nginx waits between reads while receiving the upload from the browser.
  • proxy_read_timeout - how long nginx waits for ORDS to respond once the request is handed over.
  • proxy_send_timeout - how long nginx allows for transmitting the request to ORDS.

An APEX import is not a quick file copy. The database parses the export and creates pages, regions, items and shared components, and the HTTP connection stays open for all of it.

On a slow office upload, a 40 MB export over a 5 Mbps link takes over a minute just to arrive. Default timeouts of 60 seconds will cut that off mid-transfer.

Set all three to 300s and the problem stops being a coin toss.

6. Reload, Not Restart - and the Trap That Fooled Me

Use systemctl reload nginx. A reload starts new worker processes with the new configuration and retires the old ones gracefully, so connections already in flight are not dropped.

A restart tears the whole service down. On a shared APEX instance that means every developer loses their session mid-edit, for no benefit whatsoever.

Now the trap. I wanted to confirm a reload had actually happened, so I compared the config file's timestamp to the nginx master process start time. The master had started ten days earlier, so I concluded the new config was not loaded.

That conclusion was wrong. A reload keeps the same master process and PID - it only replaces the workers. The master's start time tells you when nginx was last restarted, which says nothing at all about reloads.

The right check compares worker start times against the config file:

# when was the config last written?
stat -c %y /etc/nginx/nginx.conf

# when did the CURRENT workers start?
ps -eo pid,lstart,cmd | grep '[n]ginx: worker'

Workers newer than the config file means your change is live. Workers older than it means you edited and forgot to reload.

Better still, skip the forensics and test the behaviour directly.

7. How to Prove the Limit Is Actually Live

Configuration files describe intent. A test tells you what the running server does. This one takes fifteen seconds:

# make a 5 MB dummy payload
dd if=/dev/zero of=/tmp/blob.bin bs=1M count=5

# POST it through the proxy to a path that does not exist
curl -s -o /dev/null -w "%{http_code}\n" \
     -X POST --data-binary @/tmp/blob.bin \
     http://127.0.0.1/ords/__sizetest__

rm -f /tmp/blob.bin

Read the result like this:

  • 404 - success. nginx accepted a 5 MB body and passed it to ORDS, which correctly reported that no such path exists. With the 1 MB default, this request would never have reached ORDS at all.
  • 413 - your new limit is not active. Either the reload did not happen, or a more specific block is overriding it.

I like this test because the "good" answer is an error. You are not checking whether the path works; you are checking which layer answered, and a 404 from ORDS is proof the proxy let the body through.

Point it at a deliberately fake path like __sizetest__ so you never touch real APEX endpoints or session state.

One more source of truth worth knowing. When nginx does reject a body, it writes a specific line to the error log with the exact byte count:

grep 'too large body' /var/log/nginx/error.log | tail
grep -c ' 413 ' /var/log/nginx/access.log

If those come back empty while your browser still shows 413, the request never reached the server - you are looking at a cached error page. Hard-refresh and start the import again rather than reloading the failed URL.

An engineer checking a laptop beside server racks, verifying that the nginx upload limit is live rather than assuming it
Verify on the running system. Config files describe intent, not behaviour. Photo: Christina Morillo / Pexels

8. The 413 Cleared but the Import Still Fails

Good news, even if it does not feel like it. A different error means you have moved down a layer.

ORDS normally runs inside Tomcat, and Tomcat has its own opinions about request bodies. Two connector attributes matter:

  • maxPostSize - caps the form data the container will parse. Raise it, or set -1 to remove the cap.
  • maxSwallowSize - caps how much of a request body Tomcat will read and discard when it decides to abort. This is the one people miss.

That second one deserves a warning, because its symptom is so misleading. When maxSwallowSize is exceeded Tomcat closes the connection rather than returning a tidy error page.

Your browser then shows a connection reset or an empty response, which looks like a network fault. Engineers go hunting for firewalls and dropped packets when the answer is one attribute in server.xml.

If you run ORDS in standalone mode instead, the equivalent request-size limit lives in the ORDS standalone configuration rather than Tomcat's. The principle is identical: every layer in the path gets a vote, and the smallest limit wins.

9. The Better Path for Big Applications: Skip HTTP Entirely

If you are importing a genuinely large application, or you will repeat this import across environments, stop fighting the web tier.

An APEX export is a SQL script. You can run it directly on the database server, where no HTTP limit exists at all:

-- set the target before running the export file
BEGIN
  apex_application_install.set_workspace_id( 1234567890 );
  apex_application_install.generate_offset;
  apex_application_install.set_application_id( 500 );
  apex_application_install.set_application_alias( 'MYAPP_DEV' );
END;
/

@f500.sql

Run that with SQLcl or SQL*Plus, having copied the export file to the server first.

Three reasons this is often the right answer. There is no proxy, no container and no timeout to tune. It is scriptable, so promoting an application from dev to test to production becomes a repeatable step instead of a manual upload.

And it removes the browser from a long-running database operation, which is where most mysterious failures come from in the first place.

The browser import is genuinely convenient for small applications and one-off work. For anything you will do more than twice, the SQL path is faster and far more predictable.

10. The Security Problem This Usually Uncovers

Fixing a 413 means opening up the config of a server that is often more exposed than its owner realises. While you are in there, spend two minutes on this.

Notice that the proxy in section 3 forwards to 127.0.0.1:8080. That is correct and deliberate: nginx is the front door, and ORDS should only be reachable through it.

Now check what ORDS is actually bound to:

ss -lntp | grep -E ':8080|:1521'

If you see *:8080 or 0.0.0.0:8080 rather than 127.0.0.1:8080, then ORDS is listening on every interface. Anyone who can reach the host can bypass nginx completely - along with the size limit you just configured, and any other protection you put there.

The line to look hardest at is the other one. *:1521 means your database listener accepts connections from anywhere, and on a public IP that is a serious exposure rather than a tuning detail.

Two more checks worth running on the same visit:

  • Is a host firewall even running? systemctl is-active firewalld. "inactive" means nothing is filtering anything.
  • Is APEX Builder served over plain HTTP? If so, workspace credentials and session cookies cross the network in clear text - and Builder is a full development console, not a read-only app.

None of this is exotic. It is the same ground I cover in Oracle database security and infrastructure design, and it is usually found in the first ten minutes of looking - which is rather the point.

If you would like to know what your own estate exposes to the internet before someone else checks, that is exactly what a digital exposure audit is for.

11. The Five-Minute Checklist

  1. Read the error page footer. A server version at the bottom means the proxy answered, not APEX.
  2. Back up nginx.conf, then add client_max_body_size in the http block.
  3. Add client_body_timeout, proxy_read_timeout and proxy_send_timeout at 300s.
  4. grep -rn "client_max_body_size" /etc/nginx/ to catch a smaller override in a more specific block.
  5. nginx -t, then systemctl reload nginx. Never restart.
  6. POST a 5 MB dummy file to a fake path. A 404 proves the limit is live.
  7. Hard-refresh the browser before retrying - stale 413 pages cache and waste your afternoon.
  8. If a new error appears, move down a layer to Tomcat's maxPostSize and maxSwallowSize.
  9. While you are there, check what :8080 and :1521 are bound to.

Frequently Asked Questions

What does 413 Request Entity Too Large mean in Oracle APEX?

It means a web server in front of APEX rejected your upload because the request body exceeded its configured maximum. APEX and ORDS never saw the file. On an nginx reverse proxy the default limit is only 1 MB, so almost any real application export trips it.

Where should client_max_body_size go in nginx?

Put it in the http block if you want every virtual host to inherit it, which is the safest choice when your ORDS proxy lives in a separate file under conf.d. You can also set it per server or per location. The most specific block always wins, so a small value inside your ORDS server block will override a large one set globally.

Do I need to restart nginx after changing client_max_body_size?

No. Run nginx -t to validate the configuration, then systemctl reload nginx. A reload replaces the worker processes without dropping active connections, so live ORDS and APEX sessions survive. A full restart is unnecessary and will interrupt users.

How can I prove the new nginx limit is actually active?

Create a dummy file with dd if=/dev/zero of=/tmp/blob.bin bs=1M count=5, then POST it to any non-existent path through the proxy with curl. A 404 proves nginx accepted the 5 MB body and passed the request on. A 413 proves your new limit is not live yet.

The 413 is gone but my APEX import still fails. What now?

You have moved down a layer, which is progress. Look at ORDS and its container next: under Tomcat, the connector attributes maxPostSize and maxSwallowSize both cap request bodies, and maxSwallowSize is the one most people forget. It shows up as a confusing connection reset rather than a clean error page.

Is there a way to import a large APEX application without HTTP at all?

Yes, and for very large applications it is the better path. Copy the export file to the database server and run it with SQLcl or SQL*Plus, using the APEX_APPLICATION_INSTALL package to set the target workspace and application ID first. No web tier is involved, so no size limit applies and the process is scriptable and repeatable.

APEX, ORDS or WebLogic misbehaving in production?

I have spent 18+ years untangling exactly this kind of multi-layer problem - proxy, container, listener, database. If your APEX estate is fragile, slow, or exposed, let us look at it properly.

📩 Book a Consultation
Nasir Uddin Khan — Oracle DBA Consultant

Nasir Uddin Khan

Nasir is an Oracle Certified Professional and CSV-certified IT consultant based in Dhaka, Bangladesh. He has 18+ years of hands-on experience in Oracle database administration (RAC, Data Guard, RMAN), WebLogic middleware, ERP system design, and AI integration for manufacturing, pharmaceutical, banking, and healthcare organisations worldwide.

References & Further Reading

The commands and diagnostic method in this article come from resolving this exact failure on a production Oracle 19c host running ORDS behind nginx, in July 2026.

Related Articles

Web Tier and Database, Diagnosed Together

APEX & ORDS deployment · nginx and WebLogic tuning · listener hardening · exposure audits. 18+ years of Oracle experience. Bangladesh and worldwide.

💬