kubernetes/kops · error

failed to delete server %q: %w

Error message

failed to delete server %q: %w

What it means

Fires in deleteServer after the Hetzner hcloud API Delete call on an already-powered-off server returns an error. It wraps the underlying API failure (rate limit, auth, server not found, or server still running) so instance-deletion tasks surface why the Hetzner server could not be removed.

Source

Thrown at upup/pkg/fi/cloudup/hetzner/cloud.go:288

		return fmt.Errorf("failed to stop server %q: %w", strconv.FormatInt(id, 10), err)
	}

	for i := 1; i <= 30; i++ {
		server, _, err := client.GetByID(ctx, id)
		if err != nil || server == nil {
			return fmt.Errorf("failed to get info for server %q: %w", strconv.FormatInt(id, 10), err)
		}

		if server.Status == hcloud.ServerStatusOff {
			break
		}

		time.Sleep(time.Second * 5)
	}

	_, err = client.Delete(ctx, server)
	if err != nil {
		return fmt.Errorf("failed to delete server %q: %w", strconv.FormatInt(id, 10), err)
	}

	return nil
}

func (c *hetznerCloudImplementation) DetachInstance(instance *cloudinstances.CloudInstance) error {
	// Hetzner Cloud API doesn't provide the option of using cloud groups
	return nil
}

// ProviderID returns the kOps API identifier for Hetzner Cloud
func (c *hetznerCloudImplementation) ProviderID() kops.CloudProviderID {
	return kops.CloudProviderHetzner
}

// Region returns the Hetzner Cloud region
func (c *hetznerCloudImplementation) Region() string {
	return c.region

View on GitHub (pinned to 4c8573c808)

Solutions

  1. Check if the server still exists; treat not_found as success and reconcile state
  2. Disable delete protection on the server (and its attached volumes/primary IPs) in Hetzner console or API
  3. Ensure the token has write permissions and no other process is deleting the server concurrently
  4. Retry the delete on transient errors

Example fix

// before
if err != nil {
	return fmt.Errorf("failed to delete server %q: %w", strconv.FormatInt(id, 10), err)
}
// after
if err != nil {
	if hcloud.IsError(err, hcloud.ErrorCodeNotFound) {
		return nil // already deleted elsewhere
	}
	return fmt.Errorf("failed to delete server %q: %w", strconv.FormatInt(id, 10), err)
}
Defensive patterns

Strategy: type-guard

Validate before calling

server, _, err := client.GetByID(ctx, id)
if server == nil {
	return nil // already deleted; no Delete call needed
}
if server.Protection.Delete {
	return fmt.Errorf("server %d has delete protection enabled", id)
}

Type guard

var hcloudErr *hcloud.Error
if errors.As(err, &hcloudErr) && hcloudErr.Code == hcloud.ErrorCodeNotFound {
	// treat as success: already deleted
}

Try / catch

_, err = client.Delete(ctx, server)
if err != nil {
	var hErr *hcloud.Error
	if errors.As(err, &hErr) && hErr.Code == hcloud.ErrorCodeNotFound {
		return nil
	}
	return fmt.Errorf("failed to delete server %q: %w", strconv.FormatInt(id, 10), err)
}

Prevention

When it happens

Trigger: Calling DeleteInstance/DeleteGroup when the server was already deleted (404 not_found), the token lacks delete permission, delete is blocked (e.g. protected server or attached primary IP/volume with delete protection), or transient API errors.

Common situations: Concurrent deletion by another kOps run or manual console deletion; servers with delete protection enabled; Hetzner API outages right after shutdown completes.

Related errors


AI-assisted analysis of kubernetes/kops@4c8573c808 (2026-09-05). Data as JSON: /api/errors/e783afe3873e3d27. Report an issue: GitHub.