hashicorp/terraform · error
Invalid URL: must be
Error message
Invalid URL: %v must be: %v
What it means
When the optional COS backend 'endpoint' attribute is set, configure() validates it against the strict regex ^(http(s)?)://cos-internal\.([^.]+)\.tencentcos\.cn$ (4 capture groups). Any non-matching endpoint is rejected. The endpoint is intended only for TencentCloud internal (VPC) access; public access is built from region+bucket.
Solutions
- Use the internal form: http://cos-internal.ap-beijing.tencentcos.cn or https://cos-internal.ap-beijing.tencentcos.cn.
- If you want the public endpoint, omit endpoint entirely and rely on region+bucket+accelerate.
Example fix
// before
terraform {
backend "cos" {
endpoint = "https://bucket.cos.ap-beijing.myqcloud.com"
}
}
// after
terraform {
backend "cos" {
region = "ap-beijing"
bucket = "bucket"
# endpoint omitted; or for VPC-internal access:
# endpoint = "https://cos-internal.ap-beijing.tencentcos.cn"
}
} Defensive patterns
Strategy: validation
Validate before calling
// Validate the COS endpoint matches the strict internal form.
var cosInternalRe = regexp.MustCompile(`^(http(s)?)://cos-internal\.([^.]+)\.tencentcos\.cn$`)
func validateCOSEndpoint(endpoint string) error {
if endpoint == "" {
return nil // endpoint is optional
}
if len(cosInternalRe.FindStringSubmatch(endpoint)) != 4 {
return fmt.Errorf("Invalid URL: %s must be: %s", endpoint, "http(s)://cos-internal.{Region}.tencentcos.cn")
}
return nil
} Prevention
- Leave endpoint unset for public COS access; the backend builds the URL from region+bucket.
- Use endpoint only for VPC-internal access, in the form http(s)://cos-internal.{Region}.tencentcos.cn.
- Don't put a bucket name or custom domain in endpoint.
- Lint backend blocks in CI against the regex.
When it happens
Trigger: terraform init with endpoint set to a value that does not match http(s)://cos-internal.{Region}.tencentcos.cn.
Common situations: User supplies the public COS endpoint in endpoint (e.g. https://bucket.cos.ap-beijing.myqcloud.com); uses a custom CDN domain; forgets the cos-internal subdomain; uses http://cos.example.com.
Related errors
- acl value invalid, expected
- key can not start and end with '/'
- prefix must not start with '/' or './'
- cannot be higher than
- cannot be lower than
AI-assisted analysis of hashicorp/terraform@d32a084675 (2026-08-11).
Data as JSON: /api/errors/0919cc1972392b7a.
Report an issue: GitHub.
Appendix: source
Thrown at internal/backend/remote-state/cos/backend.go:309
u, err = url.Parse(fmt.Sprintf("https://%s.cos.%s.myqcloud.com", b.bucket, b.region))
}
if err != nil {
return err
}
if v, ok := data.GetOk("domain"); ok {
b.domain = v.(string)
log.Printf("[DEBUG] Backend: set domain for TencentCloud API client. Domain: [%s]", b.domain)
}
// set url as endpoint when provided
// "http://{Bucket}.cos-internal.{Region}.tencentcos.cn"
if v, ok := data.GetOk("endpoint"); ok {
endpoint := v.(string)
re := regexp.MustCompile(`^(http(s)?)://cos-internal\.([^.]+)\.tencentcos\.cn$`)
matches := re.FindStringSubmatch(endpoint)
if len(matches) != 4 {
return fmt.Errorf("Invalid URL: %v must be: %v", endpoint, "http(s)://cos-internal.{Region}.tencentcos.cn")
}
protocol := matches[1]
region := matches[3]
// URL after converting
newUrl := fmt.Sprintf("%s://%s.cos-internal.%s.tencentcos.cn", protocol, b.bucket, region)
u, err = url.Parse(newUrl)
log.Printf("[DEBUG] Backend: set COS URL as: [%s]", newUrl)
}
if err != nil {
return err
}
var getProviderConfig = func(key string) string {
var str string
value, err := getConfigFromProfile(data, key)
if err == nil && value != nil {View on GitHub (pinned to d32a084675)