elastic/elasticsearch · error · UserException

2

2

Error message

This plugin was built with an older plugin structure. Contact the plugin author to remove the intermediate "elasticsearch" directory within the plugin zip.

What it means

Thrown by unzip when a ZipEntry name starts with the literal prefix `elasticsearch/`. This indicates the plugin was packaged using the pre-5.x layout that nested everything under an intermediate `elasticsearch` directory. Modern installers expect the plugin's contents at the archive root. Exit code PLUGIN_MALFORMED (2).

Source

Thrown at distribution/tools/plugin-cli/src/main/java/org/elasticsearch/plugins/cli/InstallPluginAction.java:759

            : (HttpURLConnection) checksumUrl.openConnection(this.proxy);
        if (connection.getResponseCode() == 404) {
            return null;
        }
        return checksumUrl;
    }

    private Path unzip(Path zip, Path pluginsDir) throws IOException, UserException {
        // unzip plugin to a staging temp dir

        final Path target = stagingDirectory(pluginsDir);
        pathsToDeleteOnShutdown.add(target);

        try (ZipInputStream zipInput = new ZipInputStream(Files.newInputStream(zip))) {
            ZipEntry entry;
            byte[] buffer = new byte[8192];
            while ((entry = zipInput.getNextEntry()) != null) {
                if (entry.getName().startsWith("elasticsearch/")) {
                    throw new UserException(
                        PLUGIN_MALFORMED,
                        "This plugin was built with an older plugin structure."
                            + " Contact the plugin author to remove the intermediate \"elasticsearch\" directory within the plugin zip."
                    );
                }
                Path targetFile = target.resolve(entry.getName());

                // Using the entry name as a path can result in an entry outside of the plugin dir,
                // either if the name starts with the root of the filesystem, or it is a relative
                // entry like ../whatever. This check attempts to identify both cases by first
                // normalizing the path (which removes foo/..) and ensuring the normalized entry
                // is still rooted with the target plugin directory.
                if (targetFile.normalize().startsWith(target) == false) {
                    throw new UserException(
                        PLUGIN_MALFORMED,
                        "Zip contains entry name '" + entry.getName() + "' resolving outside of plugin directory"
                    );
                }

View on GitHub (pinned to db6a809a66)

Solutions

  1. Contact the plugin author for a repackaged build (as the message says).
  2. Repackage the zip yourself by removing the top-level `elasticsearch/` directory so entries sit at the root.
  3. Use a maintained fork or the equivalent built-in module if one exists.

Example fix

# repackage
mkdir /tmp/p && unzip old-plugin.zip -d /tmp/p
cd /tmp/p/elasticsearch && zip -r ../fixed-plugin.zip . && cd ..
elasticsearch-plugin install file:///tmp/fixed-plugin.zip
Defensive patterns

Strategy: validation

Validate before calling

try (ZipInputStream zis = new ZipInputStream(Files.newInputStream(zip))) {
    ZipEntry e;
    while ((e = zis.getNextEntry()) != null) {
        if (e.getName().startsWith("elasticsearch/")) {
            throw new IllegalStateException("Plugin uses legacy layout with intermediate elasticsearch/ dir");
        }
    }
}

Prevention

When it happens

Trigger: Installing a plugin zip that someone assembled from an old Elasticsearch source tree; a third-party plugin that hasn't been repackaged for current ES versions.

Common situations: Legacy/unmaintained plugins; zips produced by old build tooling; archives copied from pre-5.x example trees.

Related errors


AI-assisted analysis of elastic/elasticsearch@db6a809a66 (2026-08-12). Data as JSON: /api/errors/1022880c9b7a27ad. Report an issue: GitHub.