Proxy Caching, Negative Cache, Remote Storage, Routing Rules, Repository Health, and Failure Behavior: Guided Hands-On Workflow and Core Operations
Run a disposable Maven proxy experiment that observes cache fill, repeat reads, negative cache, scoped TTL changes, routing controls, and a safe upstream-unavailable simulation without modifying public infrastructure.
Learning objectives
- Create a disposable Maven proxy and group without changing a production repository.
- Measure first fetch and repeat fetch while verifying component/asset state independently.
- Observe a negative-cache candidate and change only disposable TTL settings.
- Simulate an upstream outage safely with the proxy Blocked control and prove cached-versus-uncached behavior.
- Test a routing rule before assignment and explain exactly which state each action changes.
Disposable-lab boundary. Use an already-running disposable Nexus Community instance on loopback/private networking. The examples use the current 3.95.2 image line as a reference but require you to record your actual running server version. Do not run this experiment against an organization's shared Maven proxy. The lab never alters Maven Central, DNS, firewalls or operating-system routes.
1. Create a local evidence and credential boundary
export NX_URL="http://127.0.0.1:8081"
export LAB="${TMPDIR:-/tmp}/nexus-ch13"
rm -rf "$LAB"
mkdir -p "$LAB"/{evidence,downloads}
chmod 700 "$LAB"
curl -fsS "$NX_URL/service/rest/v1/status" | tee "$LAB/evidence/status.txt"
read -r -p 'Disposable Nexus username: ' NX_USER
read -r -s -p 'Disposable Nexus password: ' NX_PASS; echo
export NX_AUTH_FILE="$LAB/nexus.netrc"
umask 077
printf 'machine 127.0.0.1 login %s password %s
' "$NX_USER" "$NX_PASS" > "$NX_AUTH_FILE"
unset NX_PASS
curl -fsS --netrc-file "$NX_AUTH_FILE" "$NX_URL/service/rest/v1/repositories" | tee "$LAB/evidence/repositories-before.json"
Windows note: use a temporary directory under
$env:TEMP, protect any credential file with Windows
ACLs, and use curl.exe if PowerShell aliases
curl. Do not place a password in a command-line URL or
a checked-in Maven settings file.
2. Create a purpose-built proxy and group
In Settings → Repository → Repositories create
academy-ch13-central-proxy using
maven2 (proxy) with these lab values:
| Field | Lab value | Why |
|---|---|---|
| Remote storage | https://repo1.maven.org/maven2/ |
Known public Maven Central endpoint; read-only upstream. |
| Version policy | Release | The coordinates used here are releases. |
| Layout policy | Strict | Keeps path semantics predictable. |
| Maximum Component Age | 60 minutes | Long enough to demonstrate a stable positive cache. |
| Maximum Metadata Age | 5 minutes | Makes metadata freshness deliberately different from component age. |
| Negative Cache | Enabled, TTL 5 minutes | Short disposable interval for observation; not a production recommendation. |
| Auto Blocking | Enabled | Preserves normal resilience behavior. |
| Blocked | Unchecked | Outbound access is initially allowed. |
Create academy-ch13-public using
maven2 (group) with the proxy as its only member. A
group is used so the consumer endpoint resembles a production read
path, while all remote behavior still belongs to the member proxy.
3. Prove the artifact is not already cached
The known artifact is the JUnit 4.13.2 POM. Query the proxy's assets before requesting it:
curl -fsS --netrc-file "$NX_AUTH_FILE" "$NX_URL/service/rest/v1/assets?repository=academy-ch13-central-proxy" > "$LAB/evidence/assets-before.json"
grep -F 'junit-4.13.2.pom' "$LAB/evidence/assets-before.json" && echo 'STOP: artifact was already cached; recreate the disposable proxy' || echo 'Expected: JUnit POM absent before first fetch'
If the artifact is already present, recreate the disposable proxy or choose another well-known release. A “first fetch” experiment is invalid if the object already exists in the proxy cache.
4. Fetch twice and compare byte identity plus timing
export JUNIT_PATH="junit/junit/4.13.2/junit-4.13.2.pom"
export GROUP_URL="$NX_URL/repository/academy-ch13-public"
curl -sS --netrc-file "$NX_AUTH_FILE" -o "$LAB/downloads/junit-first.pom" -w 'first http=%{http_code} total=%{time_total} size=%{size_download}
' "$GROUP_URL/$JUNIT_PATH" | tee "$LAB/evidence/junit-first-metrics.txt"
curl -sS --netrc-file "$NX_AUTH_FILE" -o "$LAB/downloads/junit-second.pom" -w 'second http=%{http_code} total=%{time_total} size=%{size_download}
' "$GROUP_URL/$JUNIT_PATH" | tee "$LAB/evidence/junit-second-metrics.txt"
sha256sum "$LAB/downloads/junit-first.pom" "$LAB/downloads/junit-second.pom" | tee "$LAB/evidence/junit-sha256.txt"
cmp -s "$LAB/downloads/junit-first.pom" "$LAB/downloads/junit-second.pom" && echo 'byte identity: PASS' | tee "$LAB/evidence/junit-byte-identity.txt"
Timing is only supporting evidence; a fast second request is not itself proof of a cache hit. Stronger evidence is the before/after repository state plus identical response bytes and the known cache policy.
curl -fsS --netrc-file "$NX_AUTH_FILE" "$NX_URL/service/rest/v1/assets?repository=academy-ch13-central-proxy" | tee "$LAB/evidence/assets-after-junit.json"
grep -F 'junit-4.13.2.pom' "$LAB/evidence/assets-after-junit.json"
5. Observe a negative-cache candidate without inventing an upstream package
export MISS_PATH="dev/academy/nonexistent/nexus-ch13-never-publish/0.0.0/nexus-ch13-never-publish-0.0.0.pom"
for n in 1 2; do
curl -sS --netrc-file "$NX_AUTH_FILE" -o "$LAB/evidence/miss-$n-body.txt" -w "miss-$n http=%{http_code} total=%{time_total}
" "$GROUP_URL/$MISS_PATH" | tee "$LAB/evidence/miss-$n-metrics.txt"
done
Both responses should remain not-found. The second may be faster, but do not claim a negative-cache hit solely from latency. The causal evidence is: the path is absent from the proxy assets, the negative cache is enabled with a known TTL, and Nexus request/log evidence—if you enable temporary logging on the disposable instance—can show whether another remote attempt occurred.
6. Change one disposable variable: negative TTL or scoped invalidation
First record the current proxy fields. Then choose one of these experiments, not both at once:
- Set Not Found Cache TTL from 5 minutes to 1 minute, save, wait for expiry, then repeat the missing path.
- Use the repository's Invalidate Cache action, then repeat the missing path immediately.
Invalidate Cache expires component/metadata ages and purges the not-found cache. It does not delete the JUnit blob. Verify that JUnit remains present in the Assets API after invalidation.
7. Simulate upstream unavailability with Blocked
Edit only academy-ch13-central-proxy and check
Blocked. This is the lab's outage injection: Nexus
will not send outbound requests to Maven Central, but cached content
remains eligible to serve.
# Cached JUnit should remain available.
curl -sS --netrc-file "$NX_AUTH_FILE" -o "$LAB/downloads/junit-while-blocked.pom" -w 'cached-while-blocked http=%{http_code} total=%{time_total}
' "$GROUP_URL/$JUNIT_PATH" | tee "$LAB/evidence/blocked-cached.txt"
# This well-known artifact was not requested earlier in the lab.
export FRESH_PATH="org/hamcrest/hamcrest/2.2/hamcrest-2.2.pom"
curl -sS --netrc-file "$NX_AUTH_FILE" -o "$LAB/evidence/blocked-uncached-body.txt" -w 'uncached-while-blocked http=%{http_code} total=%{time_total}
' "$GROUP_URL/$FRESH_PATH" | tee "$LAB/evidence/blocked-uncached.txt"
Record the actual status returned by your pinned build rather than hard-coding a universal error code. The invariant is behavioral: cached JUnit remains available; Nexus does not retrieve the previously uncached Hamcrest POM while the proxy is manually blocked. Clear Blocked afterward and verify Hamcrest can then be requested successfully.
8. Build and test a routing rule before assigning it
Navigate to Settings → Repository → Routing Rules. Create a
disposable BLOCK rule named
academy-ch13-block-internal with a simple Java regular
expression such as .*dev/academy/internal/.*. Use the
built-in test panel with both a matching internal path and the JUnit
path. Only after the tester behaves as predicted should you assign
the rule to the disposable proxy.
The rule affects remote requests from that proxy. It does not delete existing cached content, change group order, or create a content-selector privilege.
9. Challenge: choose the right control
You need to stop public fallback for
dev.academy.internal while still allowing all other
Central dependencies. Which control belongs at the remote egress
boundary: shorter cache age, group reordering, negative TTL, or a
proxy routing rule? Write your answer and expected request evidence
before applying anything.
10. Safe cleanup
Unassign and delete the disposable routing rule, delete
academy-ch13-public, then delete
academy-ch13-central-proxy through Nexus-supported
UI/API operations. Confirm the repository names disappear from
/service/rest/v1/repositories. Never remove cached
Maven files directly from the blob store or database.
rm -f "$NX_AUTH_FILE"
unset NX_AUTH_FILE NX_USER JUNIT_PATH GROUP_URL MISS_PATH FRESH_PATH
# Keep the evidence directory if you are reviewing it; otherwise remove only this lab path.
# rm -rf "$LAB"
11. Knowledge check
Why is the first-vs-second curl timing not sufficient proof of caching?
Network and runtime variance can change latency. Combine timing with before/after proxy asset state, identical bytes and known cache policy.
What should happen to a cached component when you manually Block its proxy repository?
It remains servable from local cache; the block prevents outbound remote requests.
Why should a routing rule be tested before assignment?
A matcher mistake can block legitimate dependency paths. The tester validates expected path behavior without mutating repository traffic first.
Why is deleting proxy blob files an invalid refresh technique?
Blob and database metadata must remain consistent. Use supported cache invalidation or repository controls, never direct storage edits.
What does shortening negative TTL trade away?
It discovers newly published upstream content sooner but increases outbound not-found traffic and potential upstream throttling.
12. Summary and next step
You have now observed positive cache fill, repeated reads, negative-cache behavior, scoped cache control, manual outage injection, and routing-rule testing. Lesson 3 converts those mechanics into design decisions for production proxy topology.
Official references and version notes
- Sonatype: Repository Types.
- Sonatype: Configurable Repository Fields — proxy cache ages, negative cache, blocked and auto-blocking fields.
- Sonatype: Repository Actions — Invalidate Cache, Rebuild Index and HealthCheck actions.
- Sonatype: Routing Rules.
- Sonatype: HTTP Request and Proxy Settings.
- Sonatype: Repository Health Check and Self-Hosted Feature Matrix.
- Sonatype: Securing Nexus Repository — current private-network/SSRF controls for remote URLs.
- Sonatype: Nexus Repository API Reference.
- Sonatype verified Nexus Docker image tags.
Version-sensitive statements were rechecked on 2026-08-26.
Sonatype's verified container registry exposes Nexus Repository
3.95.2 as the current latest image line, while the
archive-download documentation may lag at 3.94.1. This chapter
therefore records the
actual running server version as authoritative
evidence and uses 3.95.2 only as the concrete current reference
baseline. The mandatory work requires Community-compatible proxy
repositories, routing rules, cache controls and the Repository
Health Check summary only; no Pro-only detailed RHC report, staging,
HA, user tokens, export/import or Repository Firewall capability is
required.
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this
address.