Apache and Tomcat Load Test on a VPS: Bottlenecks and Results


Publication Date:

Updated:


INFORMATION > Apache and Tomcat Load Test on a VPS: Bottlenecks and Results

Bottom line: In an Apache-to-Tomcat stack, the first limit can be the client, proxy workers, connector threads, JVM heap or garbage collection, application code, database, or VPS itself. A single concurrency number cannot identify capacity without latency, error, and resource evidence.

What you'll learn

  • What the original two-tier VPS experiment measured.
  • How proxy and Tomcat connector limits interact with application response time.
  • What to record in a modern test: percentiles, throughput, failures, CPU, memory, GC, threads, and downstream saturation.

Who this is for: Java web teams learning how to design a small, evidence-based capacity test.

2026 context: The results below belong to the author's original test and were not reproduced for this editorial update. Treat them as a case study, not verified current performance or a VPS purchasing recommendation.

Overview

This historical test measures how many simultaneous requests an Apache and Tomcat stack handled in the author’s environment. Treat the results as a case study, not as current VPS sizing guidance.

This test covers Apache HTTP Server with Tomcat. The related Apache-only test provides a simpler comparison.

I measured the maximum number of cases that the web server (Apache) can handle simultaneously using a VPS rental server.

Table of Contents

  1. Test setup
  2. Measurement result details
  3. Conclusion

1. Test setup

1-1. Test environment

The following is the environment in which the measurements will be made.

■Rental Server Information

CPU2core
memory1GB
SSD50GB

■Server Information

OSCentOS 7.4 64bit
WEB ServerApache HTTP Server 2.4.41
AP ServerApache Tomcat 9.0.27
DB ServerPostgreSQL 10.2
JavaOpenJDK 11

1-2. Test method

The test uses JMeter, a Java load-testing tool, to increase the number of simultaneous requests until the server can no longer keep up.

Specific conditions include

  • Request Interval — 5 sec.
  • Number of simultaneous requests — 10 cases (Gradually increase the number of measurements by 10 cases each.)
  • Measurement Time — 60 sec.

With a 60-second duration and a 5-second interval, each thread sends 12 requests (60 ÷ 5).

1-3. Observed results

In the end, many people may be wondering about the conclusion of what specs the server can handle and how much it can handle.

CPU: 2core
memory: 1GB
SSD: 50GB
Up to 80 simultaneous requests can be processed

These results apply only to the tested endpoint, Apache and JVM settings, and historical VPS environment. They identify a likely bottleneck but do not size another application.

CPU: 1core
memory: 512MB
SSD: 25GB
Up to 20 simultaneous requests can be processed
CPU: 2core
memory: 1GB
SSD: 50GB
Up to 80 simultaneous requests can be processed
CPU: 3core
memory: 2GB
SSD: 100GB
Up to 200 simultaneous requests can be handled

In this specific test, the 1-core, 512 MB instance completed the scenario at roughly 20 concurrent clients before the selected failure threshold was reached.

2. Observed result details

The detailed measurements below show how that observation was derived.

2-1. Apache and Tomcat test

I threw a request with a scenario to log in from the login screen and display the list screen after logging in. Incidentally, the screen, including the authentication function, was created using the Spring framework.

The measurements yielded the following results.

  • For 10 simultaneous requests⇒OK
  • For 20 simultaneous requests⇒OK
  • For 30 simultaneous requests⇒OK
  • For 40 simultaneous requests⇒OK
  • For 50 simultaneous requests⇒OK
  • For 60 simultaneous requests⇒OK
  • For 70 simultaneous requests⇒OK
  • For 80 simultaneous requests⇒OK
  • For 90 simultaneous requests⇒NG

Error occurred on the 90th case. The cause was a connection error to Apache. The status of the server at this time was as follows.

  • CPU utilization — 26%
  • memory utilization — 100%

The complete lack of memory was the bottleneck.

This is a bit geeky, but here is how Apache's Multi-Processing Module (MPM) is configured. MPM is simply a setting for how much Apache is allowed to process in parallel.

<IfModule mpm_prefork_module>
    StartServers             5
    MinSpareServers          5
    MaxSpareServers         10
    MaxRequestWorkers      250
    MaxConnectionsPerChild   0
</IfModule>

The setting of interest is "250" for "MaxRequestWorkers". This is a setting for the maximum number of cases Apache can process at the same time. The maximum number of parallel processing is "250," so it seems that 250 cases can be processed in parallel, but looking at the memory usage, about 8 MB of memory was used per case.

It seems that Java uses 320MB of memory (248M for heap and 72M for metaspace) and Apache uses 640MB of memory (8M x 80 processes), and although Apache's MPM setting value is "250", it seems that it cannot create more than "80" processes.
Note: Since the server has 1G of memory, the total memory for Apache and Java is 960MB, which is almost at the maximum limit.

The AP server (Java side) was working fine, so the bottleneck was the WEB server (Apache).

When Apache and Tomcat are installed and running, we found that with the specifications "CPU: 2core, memory: 1GB, SSD: 50GB", the number of simultaneous accesses is limited to "80". (The assumption is that the memory settings for Java are 248M for heap and 72M for metaspace.)

2-2. consideration

The measurements suggest that memory constrained concurrency in this environment. Additional memory might permit more workers, but Apache and JVM settings would need to be retuned and the same workload tested again.

【present (memory1GB)】

  • memory: 1GB
  • Number of Apache threads: 80 cases
  • Memory consumption per thread in Apache: 8MB
  • Apache memory consumption: 640MB (80 cases×8MB)
  • Tomcat memory consumption: 320MB (heap is 248M, 72M for metaspace)
  • Memory consumption of Apache+Tomcat: 960MB

【After change (memory2GB)】

  • memory: 2GB
  • Number of Apache threads: 200 cases
  • Memory consumption per thread in Apache: 8MB
  • Apache memory consumption: 1600MB (200 cases×8MB)
  • Tomcat memory consumption: 320MB (heap is 248M, 72M for metaspace)
  • Memory consumption of Apache+Tomcat: 1920MB

A linear estimate would suggest about 200 concurrent requests with 2 GB, but that value was not measured. Increasing memory can expose other limits, including MaxRequestWorkers, CPU, connector queues, the JVM heap, garbage collection, and the application itself. Any new size requires another load test.

【After change (memory512GB)】

  • memory: 512GB
  • Number of Apache threads: 20 cases
  • Memory consumption per thread in Apache: 8MB
  • Apache memory consumption: 160MB (20 cases×8MB)
  • Tomcat memory consumption: 320MB (heap is 248M, 72M for metaspace)
  • Memory consumption of Apache+Tomcat: 480MB

3. Conclusion

The historical Apache-and-Tomcat test reached its selected threshold at about 20 concurrent clients on a 1-core, 512 MB instance. This does not establish a safe number of real users for another application.

No competing workload was included. Production sizing requires representative traffic, resource monitoring, explicit failure criteria, and capacity headroom.

Official references